seamusabshere-date-performance 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/AUTHORS +1 -0
- data/BENCHMARKS +30 -0
- data/README.md +134 -0
- data/Rakefile +105 -0
- data/VERSION.yml +4 -0
- data/ext/date_performance.c +440 -0
- data/ext/extconf.rb +9 -0
- data/lib/date/memoize.rb +86 -0
- data/lib/date/performance.rb +44 -0
- data/test/benchmarks.rb +68 -0
- data/test/date_memoize_test.rb +70 -0
- data/test/extension_test.rb +145 -0
- metadata +67 -0
data/AUTHORS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rtomayko = Ryan Tomayko <rtomayko@gmail.com>
|
data/BENCHMARKS
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
== WITH EXTENSION ==============================
|
2
|
+
comparison
|
3
|
+
0.030000 0.000000 0.030000 ( 0.027561)
|
4
|
+
greater_than
|
5
|
+
0.030000 0.000000 0.030000 ( 0.026667)
|
6
|
+
new_civil
|
7
|
+
0.060000 0.000000 0.060000 ( 0.063149)
|
8
|
+
new_jd
|
9
|
+
0.060000 0.000000 0.060000 ( 0.058935)
|
10
|
+
strftime
|
11
|
+
0.020000 0.000000 0.020000 ( 0.018389)
|
12
|
+
strftime_after_new
|
13
|
+
0.080000 0.000000 0.080000 ( 0.082086)
|
14
|
+
strptime
|
15
|
+
0.070000 0.000000 0.070000 ( 0.068235)
|
16
|
+
== WITHOUT EXTENSION ===========================
|
17
|
+
comparison
|
18
|
+
0.030000 0.000000 0.030000 ( 0.027135)
|
19
|
+
greater_than
|
20
|
+
0.030000 0.000000 0.030000 ( 0.026128)
|
21
|
+
new_civil
|
22
|
+
0.300000 0.000000 0.300000 ( 0.303737)
|
23
|
+
new_jd
|
24
|
+
0.110000 0.000000 0.110000 ( 0.110033)
|
25
|
+
strftime
|
26
|
+
0.230000 0.000000 0.230000 ( 0.231178)
|
27
|
+
strftime_after_new
|
28
|
+
0.850000 0.000000 0.850000 ( 0.843894)
|
29
|
+
strptime
|
30
|
+
3.000000 0.030000 3.030000 ( 3.033587)
|
data/README.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
Date::Performance
|
2
|
+
=================
|
3
|
+
|
4
|
+
This package adds some semblance of performance to Ruby's core Date class
|
5
|
+
using a combination of different techniques:
|
6
|
+
|
7
|
+
1. Implements various core *Date* methods in C. This is nowhere near a
|
8
|
+
complete rewrite of all *Date* features but many of the hot spots have
|
9
|
+
been replaced with machine code.
|
10
|
+
|
11
|
+
2. Provide alternate implementations of `strftime` and `strptime` in C. The stock
|
12
|
+
date formatting and parsing methods are extremely slow compared to their
|
13
|
+
libc counterparts. *Date#sys_strftime* and *Date::sys_strptime* are light
|
14
|
+
facades on top of the system's `strftime(2)` and `strptime(2)`. The system
|
15
|
+
methods run 10x and 50x (yes, _fifty-ecks_) faster than their Ruby based counterparts,
|
16
|
+
respectively. Unfortunately, `strftime(2)` and `strptime(2)` implementations vary from
|
17
|
+
system to system and have various limitations not found in the core Date
|
18
|
+
implementation so can not safely be used as replacements for the core methods.
|
19
|
+
|
20
|
+
3. Memoization. The *Date::Memoize* module can be used to speed certain
|
21
|
+
types of repetitive date processing significantly. This file must be
|
22
|
+
required separately.
|
23
|
+
|
24
|
+
Synopsis
|
25
|
+
--------
|
26
|
+
|
27
|
+
This package is mostly transparent after an initial require:
|
28
|
+
|
29
|
+
require 'date/performance'
|
30
|
+
Date.new 1912, 6, 23
|
31
|
+
# Wow! That was fast!
|
32
|
+
|
33
|
+
*Date::Performance* is not used directly but automatically replaces core *Date*
|
34
|
+
methods when required.
|
35
|
+
|
36
|
+
In addition to the C extension, the *Date::Memoization* module can be used to
|
37
|
+
speed things up even further in some cases by making a trade off between space
|
38
|
+
and time:
|
39
|
+
|
40
|
+
require 'date/memoize'
|
41
|
+
Date.new 1912, 6, 23
|
42
|
+
Date.parse '1912-06-23'
|
43
|
+
|
44
|
+
Requiring the file automatically replaces *Date::new* / *Date::civil*, *Date::parse*,
|
45
|
+
and *Date::jd* methods with memoized versions.
|
46
|
+
|
47
|
+
Installation / Hacking
|
48
|
+
----------------------
|
49
|
+
|
50
|
+
This package has been tested on the following platforms:
|
51
|
+
|
52
|
+
* FreeBSD 5.4 (x86) and 6.1 (AMD64)
|
53
|
+
* Linux / Fedora Core 6 (x86)
|
54
|
+
* MacOS X (Intel)
|
55
|
+
|
56
|
+
The easiest way to install the package is to use RubyGems:
|
57
|
+
|
58
|
+
$ gem install date-performance --source=http://tomayko.com
|
59
|
+
|
60
|
+
Old versions and other dist formats are available at:
|
61
|
+
|
62
|
+
http://tomayko.com/dist/date-performance/
|
63
|
+
|
64
|
+
A git repository is also available:
|
65
|
+
|
66
|
+
$ git clone git://github.com/rtomayko/date-performance.git
|
67
|
+
|
68
|
+
Background
|
69
|
+
----------
|
70
|
+
|
71
|
+
The *Date* class is often the cause of poor performance in Ruby programs. A frequent
|
72
|
+
suggestion is to use the *Time* class, which is much faster, but that solution has
|
73
|
+
correctness problems in a wide range of data typing cases. It is often the case that
|
74
|
+
you want separate *Date*, *Time*, and *DateTime* types.
|
75
|
+
|
76
|
+
There are a couple of reasons why *Date* runs slowly when compared with
|
77
|
+
*Time*. The common assumption is that this is mostly due to *Time* being
|
78
|
+
written in C and *Date* being written in Ruby. While that clearly has an
|
79
|
+
impact, I would argue that the reason *Date* is slow is because it's not
|
80
|
+
designed to be fast. The code opts for readability over performance in almost
|
81
|
+
every case. _This is a feature_.
|
82
|
+
|
83
|
+
Have you read the *date.rb* documentation [1]? The implementation is pretty
|
84
|
+
hard core; it can handle a lot of weird cases that *Time* [2] does not and
|
85
|
+
would appear to be a correct implementation of date handling, which has the
|
86
|
+
usual side-effect of being slow.
|
87
|
+
|
88
|
+
The *Date* implementation uses a single Astronomical Julian Day (AJD) number
|
89
|
+
to represent dates internally. In fact, *Date#initialize* takes a
|
90
|
+
single `ajd` argument, which means that all date forms that are commonly used
|
91
|
+
(UNIX timestamp, Civil, etc.) must be converted to an AJD before we can even
|
92
|
+
instantiate the thing.
|
93
|
+
|
94
|
+
The real performance hit seems to come from the various rational number
|
95
|
+
operations performed on the way from a civil, ordinal, and julian date to
|
96
|
+
an AJD.
|
97
|
+
|
98
|
+
When I began writing *Date::Performance*, I was getting pretty big (3x - 4x)
|
99
|
+
performance boosts in many places simply by optimizing the Ruby code a bit.
|
100
|
+
These boosts came at the expense of readability, however, and so the decision
|
101
|
+
was made to go for _maximum unreadability_ and implement the boosts in C.
|
102
|
+
|
103
|
+
There's a nice balance here: the Ruby implementation reads like a spec,
|
104
|
+
while the C version implements it for quickness.
|
105
|
+
|
106
|
+
Memoization
|
107
|
+
-----------
|
108
|
+
|
109
|
+
In addition to the C extension, this package includes a separate *Date::Memoize*
|
110
|
+
module that can further speed up date processing in situations where the range
|
111
|
+
of dates being manipulated is fairly dense and the same dates are being
|
112
|
+
created repeatedly. Working with databases and flat files are two examples
|
113
|
+
where memoization may help significantly.
|
114
|
+
|
115
|
+
The *Date::Memoize* module replaces various Date constructor methods (`new`,
|
116
|
+
`civil`, and `parse`) with memoized[http://en.wikipedia.org/wiki/Memoization]
|
117
|
+
versions (see *Date::Memoization* for details). The best way to determine
|
118
|
+
whether memoization is right for you is to add it to your project and see
|
119
|
+
what happens.
|
120
|
+
|
121
|
+
License
|
122
|
+
-------
|
123
|
+
|
124
|
+
MIT. See the COPYING file included in the distribution for more
|
125
|
+
information.
|
126
|
+
|
127
|
+
See Also
|
128
|
+
--------
|
129
|
+
|
130
|
+
* [1] Ruby *Date* Implementation Notes and Documentation:
|
131
|
+
http://www.ruby-doc.org/docs/rdoc/1.9/files/_/lib/date_rb.html
|
132
|
+
|
133
|
+
* [2] Ruby *Time* documentation
|
134
|
+
http://www.ruby-doc.org/docs/rdoc/1.9/classes/Time.html
|
data/Rakefile
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
load 'misc/asciidoc.rake'
|
2
|
+
load 'misc/project.rake'
|
3
|
+
|
4
|
+
Project.new "Date::Performance" do |p|
|
5
|
+
p.package_name = 'date-performance'
|
6
|
+
p.version_file = 'lib/date/performance.rb'
|
7
|
+
p.summary = "Adds some semblance of performance to Ruby's core Date class."
|
8
|
+
p.project_url = "http://tomayko.com/src/date-performance/"
|
9
|
+
p.extra_files.include "ext/**/*.{rb,c,h}", "AUTHORS", "BENCHMARKS"
|
10
|
+
p.configure_package {|spec| spec.extensions = FileList["ext/**/extconf.rb"].to_a }
|
11
|
+
p.author = 'Ryan Tomayko <r@tomayko.com>'
|
12
|
+
p.rubyforge_project = 'wink'
|
13
|
+
|
14
|
+
p.remote_dist_location = "gus@tomayko.com:/dist/#{p.package_name}"
|
15
|
+
p.remote_branch_location = "gus@tomayko.com:/src/#{p.package_name}.git"
|
16
|
+
p.remote_doc_location = "gus@tomayko.com:/src/#{p.package_name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
begin
|
20
|
+
require 'jeweler'
|
21
|
+
Jeweler::Tasks.new do |gemspec|
|
22
|
+
gemspec.name = 'date-performance'
|
23
|
+
gemspec.summary = "Adds some semblance of performance to Ruby's core Date class."
|
24
|
+
gemspec.email = 'Ryan Tomayko <r@tomayko.com>'
|
25
|
+
gemspec.homepage = "http://tomayko.com/src/date-performance/"
|
26
|
+
gemspec.description = "Adds some semblance of performance to Ruby's core Date class."
|
27
|
+
gemspec.authors = [ 'Ryan Tomayko', 'Alexander Dymo' ]
|
28
|
+
gemspec.files.include "ext/**/*.{rb,c,h}", "AUTHORS", "BENCHMARKS"
|
29
|
+
end
|
30
|
+
rescue LoadError
|
31
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
32
|
+
end
|
33
|
+
|
34
|
+
task :default => [ :compile, :test ]
|
35
|
+
|
36
|
+
file 'doc/index.txt' => 'README.md' do |f|
|
37
|
+
cp 'README.md', f.name
|
38
|
+
end
|
39
|
+
|
40
|
+
CLEAN.include [ "ext/*.{o,bundle}", "lib/*.{bundle,so,dll}" ]
|
41
|
+
CLOBBER.include [ "ext/Makefile" ]
|
42
|
+
|
43
|
+
if File.exist? 'misc/date-1.8.5'
|
44
|
+
desc "Run unit tests with Date from Ruby 1.8.5"
|
45
|
+
Rake::TestTask.new 'test:ruby185' do |t|
|
46
|
+
t.libs << "test"
|
47
|
+
t.libs << "misc/date-1.8.5"
|
48
|
+
t.test_files = FileList['test/*_test.rb']
|
49
|
+
t.verbose = true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
task 'benchmark.without' do |t|
|
54
|
+
verbose false do
|
55
|
+
puts '== WITHOUT EXTENSION ==========================='
|
56
|
+
ruby "-Ilib test/benchmarks.rb"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
task 'benchmark.with' do |t|
|
61
|
+
verbose false do
|
62
|
+
puts '== WITH EXTENSION =============================='
|
63
|
+
ruby "-Ilib -rdate/performance test/benchmarks.rb"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
desc "Run benchmarks"
|
68
|
+
task :benchmark => [ 'benchmark.without', 'benchmark.with' ]
|
69
|
+
|
70
|
+
# Extension =================================================================
|
71
|
+
|
72
|
+
DLEXT = Config::CONFIG['DLEXT']
|
73
|
+
|
74
|
+
directory "lib"
|
75
|
+
|
76
|
+
desc "Build the extension"
|
77
|
+
task "date_performance" => [ "lib/date_performance.#{DLEXT}" ]
|
78
|
+
|
79
|
+
file "ext/Makefile" => FileList[ "ext/*.{c,h,rb}" ] do
|
80
|
+
Dir.chdir("ext") { ruby "extconf.rb" }
|
81
|
+
end
|
82
|
+
|
83
|
+
file "ext/date_performance.#{DLEXT}" => FileList["ext/*.c", "ext/Makefile"] do |f|
|
84
|
+
Dir.chdir("ext") { sh "make" }
|
85
|
+
end
|
86
|
+
|
87
|
+
file "lib/date_performance.#{DLEXT}" => [ "ext/date_performance.#{DLEXT}" ] do |t|
|
88
|
+
cp "ext/date_performance.#{DLEXT}", t.name
|
89
|
+
end
|
90
|
+
|
91
|
+
desc "Compiles all extensions"
|
92
|
+
task :compile => [ "lib/date_performance.#{DLEXT}" ]
|
93
|
+
|
94
|
+
# Rubyforge =================================================================
|
95
|
+
|
96
|
+
VERS = Project.current.version
|
97
|
+
PKGNAME = "dist/date-performance-#{VERS}"
|
98
|
+
|
99
|
+
desc 'Publish new release to rubyforge'
|
100
|
+
task :release => [ "#{PKGNAME}.gem", "#{PKGNAME}.tar.gz" ] do |t|
|
101
|
+
sh <<-end
|
102
|
+
rubyforge add_release wink date-performance #{VERS} #{PKGNAME}.gem &&
|
103
|
+
rubyforge add_file wink date-performance #{VERS} #{PKGNAME}.tar.gz
|
104
|
+
end
|
105
|
+
end
|
data/VERSION.yml
ADDED
@@ -0,0 +1,440 @@
|
|
1
|
+
|
2
|
+
/*
|
3
|
+
* Enable C99 extensions to enable fast float rounding stuff in math.h.
|
4
|
+
*/
|
5
|
+
#define _ISOC9X_SOURCE 1
|
6
|
+
#define _ISOC99_SOURCE 1
|
7
|
+
|
8
|
+
#include <math.h>
|
9
|
+
#include <ruby.h>
|
10
|
+
#include <time.h>
|
11
|
+
|
12
|
+
#define FLOOR(a) lrintf(floorf(a))
|
13
|
+
#define FLOAT(a) (float)a
|
14
|
+
|
15
|
+
static VALUE rb_cDate; /* class Date */
|
16
|
+
static VALUE rb_cRational; /* class Rational */
|
17
|
+
|
18
|
+
static ID id_subtract; /* :- */
|
19
|
+
static ID id_add; /* :+ */
|
20
|
+
static ID id_divmod; /* :divmod */
|
21
|
+
static ID id_new; /* :new */
|
22
|
+
static ID id_new_bang; /* :new! */
|
23
|
+
static ID id_ivar_civil; /* :@__civil__ */
|
24
|
+
static ID id_jd; /* :jd */
|
25
|
+
static ID id_ivar_ajd; /* :@ajd */
|
26
|
+
static ID id_jd_to_civil; /* :jd_to_civil */
|
27
|
+
static ID id_ivar_sg; /* :@sg */
|
28
|
+
static ID id_numerator; /* :numerator */
|
29
|
+
static ID id_denominator; /* :denominator */
|
30
|
+
|
31
|
+
static ID id_strptime_without_performance;
|
32
|
+
static ID id_strftime_without_performance;
|
33
|
+
|
34
|
+
static VALUE JULIAN; /* Date::JULIAN */
|
35
|
+
static VALUE GREGORIAN; /* Date::GREGORIAN */
|
36
|
+
static VALUE ITALY; /* Date::ITALY */
|
37
|
+
|
38
|
+
static VALUE ra_one_half; /* Rational(1, 2) */
|
39
|
+
static VALUE DEFAULT_FORMAT; /* "%F" */
|
40
|
+
|
41
|
+
static int initialized = 0;
|
42
|
+
|
43
|
+
static inline int
|
44
|
+
civil_to_jd(int y, int m, int d, VALUE sg)
|
45
|
+
{
|
46
|
+
int a, b, jd;
|
47
|
+
if ( m <= 2 ) {
|
48
|
+
y-= 1;
|
49
|
+
m+= 12;
|
50
|
+
}
|
51
|
+
a = y / 100;
|
52
|
+
b = 2 - a + (a / 4);
|
53
|
+
jd = FLOOR(365.25 * (y + 4716)) +
|
54
|
+
FLOOR(30.6001 * (m + 1)) +
|
55
|
+
d + b - 1524;
|
56
|
+
if ( sg == JULIAN || (sg != GREGORIAN && jd < FIX2INT(sg)) )
|
57
|
+
jd -= b;
|
58
|
+
return jd;
|
59
|
+
}
|
60
|
+
|
61
|
+
|
62
|
+
/*
|
63
|
+
* Date::civil_to_jd(year, month, day, sg=Date::GREGORIAN)
|
64
|
+
*/
|
65
|
+
static VALUE
|
66
|
+
rb_date_civil_to_jd(int argc, VALUE* argv, VALUE self)
|
67
|
+
{
|
68
|
+
int y = FIX2INT(argv[0]),
|
69
|
+
m = FIX2INT(argv[1]),
|
70
|
+
d = FIX2INT(argv[2]);
|
71
|
+
VALUE sg = (argc == 4 ? argv[3] : GREGORIAN);
|
72
|
+
return INT2FIX(civil_to_jd(y, m, d, sg));
|
73
|
+
}
|
74
|
+
|
75
|
+
|
76
|
+
struct mini_tm {
|
77
|
+
int y;
|
78
|
+
int m;
|
79
|
+
int d;
|
80
|
+
};
|
81
|
+
|
82
|
+
|
83
|
+
static inline void
|
84
|
+
jd_to_civil(int jd, VALUE sg, struct mini_tm * t)
|
85
|
+
{
|
86
|
+
int a, b, c, d, e;
|
87
|
+
if ( sg == JULIAN || (sg != GREGORIAN && jd < FIX2INT(sg)) ) { /* julian? */
|
88
|
+
a = jd;
|
89
|
+
}else{
|
90
|
+
int x = FLOOR((jd - 1867216.25) / 36524.25);
|
91
|
+
a = jd + 1 + x - FLOOR(x / 4.0);
|
92
|
+
}
|
93
|
+
b = a + 1524;
|
94
|
+
c = FLOOR((b - 122.1) / 365.25);
|
95
|
+
d = FLOOR(365.25 * c);
|
96
|
+
e = FLOOR((b - d) / 30.6001);
|
97
|
+
t->d = b - d - FLOOR(30.6001 * e);
|
98
|
+
if ( e <= 13 ) {
|
99
|
+
t->m = e - 1;
|
100
|
+
t->y = c - 4716;
|
101
|
+
}else{
|
102
|
+
t->m = e - 13;
|
103
|
+
t->y = c - 4715;
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
|
108
|
+
/*
|
109
|
+
* Date::jd_to_civil(jd, sg=GREGORIAN)
|
110
|
+
*/
|
111
|
+
static VALUE
|
112
|
+
rb_date_jd_to_civil(int argc, VALUE * argv, VALUE self)
|
113
|
+
{
|
114
|
+
int jd = FIX2INT(argv[0]);
|
115
|
+
VALUE sg = (argc == 2 ? argv[1] : GREGORIAN);
|
116
|
+
struct mini_tm t;
|
117
|
+
jd_to_civil(jd, sg, &t);
|
118
|
+
return rb_ary_new3(3, INT2FIX(t.y), INT2FIX(t.m), INT2FIX(t.d));
|
119
|
+
}
|
120
|
+
|
121
|
+
|
122
|
+
/*
|
123
|
+
* Calculate the AJD from a julian date, fractional day, and offset.
|
124
|
+
*/
|
125
|
+
static inline VALUE
|
126
|
+
jd_to_ajd(long jd, VALUE fr, VALUE of)
|
127
|
+
{
|
128
|
+
/* Ruby Implementation: jd + fr - of - 1.to_r/2 */
|
129
|
+
if ( TYPE(fr) == T_FIXNUM && TYPE(of) == T_FIXNUM ) {
|
130
|
+
/* fast path the common case of no fraction and no offset */
|
131
|
+
long numerator = (((jd + FIX2LONG(fr) - FIX2LONG(of)) - 1) * 2) + 1;
|
132
|
+
return rb_funcall(rb_cRational, id_new, 2, LONG2FIX(numerator), LONG2FIX(2));
|
133
|
+
}else{
|
134
|
+
/* use slower rational math */
|
135
|
+
VALUE result = rb_funcall(rb_cRational, id_new, 2, LONG2FIX(jd), LONG2FIX(1));
|
136
|
+
result = rb_funcall(result, id_add, 1, fr);
|
137
|
+
if ( of != LONG2FIX(0) )
|
138
|
+
result = rb_funcall(result, id_subtract, 1, of);
|
139
|
+
result = rb_funcall(result, id_subtract, 1, ra_one_half);
|
140
|
+
return result;
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
|
145
|
+
/*
|
146
|
+
* Date::jd_to_ajd(jd, fr, of=0)
|
147
|
+
*/
|
148
|
+
static VALUE
|
149
|
+
rb_date_jd_to_ajd(int argc, VALUE * argv, VALUE self)
|
150
|
+
{
|
151
|
+
long jd = FIX2LONG(argv[0]);
|
152
|
+
VALUE fr = (argc > 1 ? argv[1] : LONG2FIX(0));
|
153
|
+
VALUE of = (argc > 2 ? argv[2] : LONG2FIX(0));
|
154
|
+
return jd_to_ajd(jd, fr, of);
|
155
|
+
}
|
156
|
+
|
157
|
+
|
158
|
+
/*
|
159
|
+
* Date::ajd_to_jd(ajd, of=0)
|
160
|
+
*
|
161
|
+
* TODO: handle offsets properly.
|
162
|
+
*
|
163
|
+
* Ruby Implementation: (ajd + of + 1.to_r/2).divmod(1)
|
164
|
+
*/
|
165
|
+
static VALUE
|
166
|
+
rb_date_ajd_to_jd(int argc, VALUE * argv, VALUE self)
|
167
|
+
{
|
168
|
+
VALUE ajd = argv[0];
|
169
|
+
VALUE of = (argc == 2 ? argv[1] : INT2FIX(0));
|
170
|
+
long den = FIX2LONG(rb_funcall(ajd, id_denominator, 0));
|
171
|
+
long num = FIX2LONG(rb_funcall(ajd, id_numerator, 0));
|
172
|
+
if ( den == 2 && of == INT2FIX(0) ) {
|
173
|
+
/* fast path */
|
174
|
+
return rb_ary_new3(2, LONG2FIX((num + 1) / 2), ra_one_half);
|
175
|
+
}else{
|
176
|
+
VALUE result = rb_funcall(ajd, id_add, 1, of);
|
177
|
+
result = rb_funcall(result, id_add, 1, ra_one_half);
|
178
|
+
return rb_funcall(result, id_divmod, 1, LONG2FIX(1));
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
|
183
|
+
/*
|
184
|
+
* Date::new(y=-4712, m=1, d=1, sg=ITALY)
|
185
|
+
*/
|
186
|
+
static VALUE
|
187
|
+
rb_date_new(int argc, VALUE * argv, VALUE self) {
|
188
|
+
int y = (argc > 0 ? NUM2INT(argv[0]) : -4712),
|
189
|
+
m = (argc > 1 ? NUM2INT(argv[1]) : 1),
|
190
|
+
d = (argc > 2 ? NUM2INT(argv[2]) : 1);
|
191
|
+
VALUE sg = (argc > 3 ? argv[3] : ITALY);
|
192
|
+
int jd = -1;
|
193
|
+
struct mini_tm t;
|
194
|
+
if (d < 0) {
|
195
|
+
int ny = (y * 12 + m) / 12;
|
196
|
+
int nm = (y * 12 + m) % 12;
|
197
|
+
nm = (nm + 1) / 1;
|
198
|
+
jd = civil_to_jd(ny, nm, d+1, sg);
|
199
|
+
|
200
|
+
VALUE ns = jd < 2299161 ? JULIAN : GREGORIAN;
|
201
|
+
jd_to_civil(jd-d, ns, &t);
|
202
|
+
if ( t.y != ny || t.m != nm || t.d != 1 ) {
|
203
|
+
rb_raise(rb_eArgError, "Invalid date: (%d, %d, %d)", y, m, d);
|
204
|
+
return Qnil;
|
205
|
+
}
|
206
|
+
jd_to_civil(jd, sg, &t);
|
207
|
+
if ( t.y != y || t.m != m ) {
|
208
|
+
rb_raise(rb_eArgError, "Invalid date: (%d, %d, %d)", y, m, d);
|
209
|
+
return Qnil;
|
210
|
+
}
|
211
|
+
} else {
|
212
|
+
jd = civil_to_jd(y, m, d, sg);
|
213
|
+
jd_to_civil(jd, sg, &t);
|
214
|
+
if ( t.y != y || t.m != m || t.d != d ) {
|
215
|
+
rb_raise(rb_eArgError, "Invalid date: (%d, %d, %d)", y, m, d);
|
216
|
+
return Qnil;
|
217
|
+
}
|
218
|
+
}
|
219
|
+
VALUE ajd = jd_to_ajd(jd, INT2FIX(0), INT2FIX(0));
|
220
|
+
VALUE date = rb_funcall(self, id_new_bang, 3, ajd, INT2FIX(0), sg);
|
221
|
+
rb_ivar_set(date, id_ivar_civil, rb_ary_new3(3, INT2FIX(t.y), INT2FIX(t.m), INT2FIX(t.d)));
|
222
|
+
return date;
|
223
|
+
}
|
224
|
+
|
225
|
+
|
226
|
+
/*
|
227
|
+
* Date#civil
|
228
|
+
*
|
229
|
+
* Fast path the case where the date is created with civil parameters.
|
230
|
+
*/
|
231
|
+
static VALUE
|
232
|
+
rb_date_civil(VALUE self) {
|
233
|
+
if ( rb_ivar_defined(self, id_ivar_civil) == Qfalse ) {
|
234
|
+
VALUE jd = rb_funcall(self, id_jd, 0);
|
235
|
+
VALUE sg = rb_ivar_get(self, id_ivar_sg);
|
236
|
+
VALUE result = rb_funcall(rb_cDate, id_jd_to_civil, 2, jd, sg);
|
237
|
+
return rb_ivar_set(self, id_ivar_civil, result);
|
238
|
+
}else{
|
239
|
+
return rb_ivar_get(self, id_ivar_civil);
|
240
|
+
}
|
241
|
+
}
|
242
|
+
|
243
|
+
|
244
|
+
/*
|
245
|
+
* Date#sys_strftime(fmt="%F")
|
246
|
+
*/
|
247
|
+
static VALUE
|
248
|
+
rb_date_strftime(int argc, VALUE * argv, VALUE self)
|
249
|
+
{
|
250
|
+
VALUE format = (argc > 0 ? *argv : DEFAULT_FORMAT);
|
251
|
+
VALUE civil = rb_date_civil(self);
|
252
|
+
|
253
|
+
char * pf = RSTRING(format)->ptr;
|
254
|
+
VALUE * pc = RARRAY(civil)->ptr;
|
255
|
+
int ic[3];
|
256
|
+
|
257
|
+
ic[0] = FIX2INT(pc[0]);
|
258
|
+
ic[1] = FIX2INT(pc[1]);
|
259
|
+
ic[2] = FIX2INT(pc[2]);
|
260
|
+
|
261
|
+
/* fast path default format: %F or %Y-%m-%d */
|
262
|
+
if ( (pf[0] == '%' && pf[1] == 'F' && pf[2] == 0) ||
|
263
|
+
(pf[0] == '%' && pf[1] == 'Y' && pf[2] == '-'
|
264
|
+
&& pf[3] == '%' && pf[4] == 'm' && pf[5] == '-'
|
265
|
+
&& pf[6] == '%' && pf[7] == 'd' && pf[8] == 0) )
|
266
|
+
{
|
267
|
+
VALUE buf = rb_str_buf_new(11);
|
268
|
+
char * pb = RSTRING(buf)->ptr;
|
269
|
+
RSTRING(buf)->len =
|
270
|
+
sprintf(pb, "%04d-%02d-%02d", ic[0], ic[1], ic[2]);
|
271
|
+
return buf;
|
272
|
+
}
|
273
|
+
|
274
|
+
/* Use libc's strftime but only for Date class */
|
275
|
+
if ( RBASIC(self)->klass == rb_cDate ){
|
276
|
+
VALUE buf = rb_str_buf_new(128);
|
277
|
+
char * pb = RSTRING(buf)->ptr;
|
278
|
+
struct tm t;
|
279
|
+
bzero(&t, sizeof(struct tm));
|
280
|
+
t.tm_year = ic[0] - 1900;
|
281
|
+
t.tm_mon = ic[1] - 1;
|
282
|
+
t.tm_mday = ic[2];
|
283
|
+
mktime(&t); /* fill in missing items (tm_wday, tm_yday) */
|
284
|
+
if ( (RSTRING(buf)->len = strftime(pb, 128, pf, &t)) > 0 )
|
285
|
+
return buf;
|
286
|
+
}
|
287
|
+
|
288
|
+
/* fall back on Ruby implementation if libc's strftime fails */
|
289
|
+
return rb_funcall2(self, id_strftime_without_performance, argc, argv);
|
290
|
+
}
|
291
|
+
|
292
|
+
|
293
|
+
/*
|
294
|
+
* Date::strptime(str="-4712-01-01", fmt='%F')
|
295
|
+
*/
|
296
|
+
static VALUE
|
297
|
+
rb_date_strptime(int argc, VALUE * argv, VALUE self)
|
298
|
+
{
|
299
|
+
char *pe;
|
300
|
+
struct tm buf;
|
301
|
+
VALUE str = (argc > 0 ? argv[0] : rb_str_new2("-4712-01-01")),
|
302
|
+
fmt = (argc > 1 ? argv[1] : DEFAULT_FORMAT),
|
303
|
+
sg = (argc > 2 ? argv[2] : ITALY);
|
304
|
+
char * ps = RSTRING(str)->ptr;
|
305
|
+
char * pf = RSTRING(fmt)->ptr;
|
306
|
+
VALUE parts[4];
|
307
|
+
|
308
|
+
/* fast path default format */
|
309
|
+
if ( (pf[0] == '%' && pf[1] == 'F' && pf[0])
|
310
|
+
|| (pf[0] == '%' && pf[1] == 'Y' && pf[2] == '-'
|
311
|
+
&& pf[3] == '%' && pf[4] == 'm' && pf[5] == '-'
|
312
|
+
&& pf[6] == '%' && pf[7] == 'd' && pf[8] == 0) )
|
313
|
+
{
|
314
|
+
parts[0] = INT2FIX(strtol(ps, &pe, 10));
|
315
|
+
parts[1] = Qnil;
|
316
|
+
parts[2] = Qnil;
|
317
|
+
parts[3] = sg;
|
318
|
+
if( pe == ps + 4 ) {
|
319
|
+
parts[1] = INT2FIX(strtol(ps + 5, &pe, 10));
|
320
|
+
if ( pe == ps + 7 ) {
|
321
|
+
parts[2] = INT2FIX(strtol(ps + 8, &pe, 10));
|
322
|
+
if ( pe == ps + 10 )
|
323
|
+
return rb_date_new(4, (VALUE*)&parts, self);
|
324
|
+
}
|
325
|
+
}
|
326
|
+
}
|
327
|
+
|
328
|
+
/* fall back on strptime(3) */
|
329
|
+
if ( strptime(ps, pf, &buf) )
|
330
|
+
{
|
331
|
+
parts[0] = INT2FIX(buf.tm_year + 1900);
|
332
|
+
parts[1] = INT2FIX(buf.tm_mon + 1);
|
333
|
+
parts[2] = INT2FIX(buf.tm_mday);
|
334
|
+
parts[3] = sg;
|
335
|
+
return rb_date_new(4, (VALUE*)&parts, self);
|
336
|
+
}
|
337
|
+
|
338
|
+
/* if that doesn't work, fall back on Ruby implementation */
|
339
|
+
return rb_funcall2(self, id_strptime_without_performance, argc, argv);
|
340
|
+
}
|
341
|
+
|
342
|
+
/*
|
343
|
+
* Date::<=>(other)
|
344
|
+
*/
|
345
|
+
static VALUE
|
346
|
+
rb_date_compare(int argc, VALUE * argv, VALUE self)
|
347
|
+
{
|
348
|
+
if (NIL_P(argv[0]))
|
349
|
+
return Qnil;
|
350
|
+
long other_den = -1;
|
351
|
+
long other_num = -1;
|
352
|
+
if (FIXNUM_P(argv[0])) {
|
353
|
+
//compare with argument as with astronomical julian day number
|
354
|
+
other_den = 1;
|
355
|
+
other_num = FIX2LONG(argv[0]);
|
356
|
+
} else if (rb_obj_is_kind_of(argv[0], rb_cDate)) {
|
357
|
+
VALUE other_date = argv[0];
|
358
|
+
VALUE other_ajd = rb_ivar_get(other_date, id_ivar_ajd);
|
359
|
+
other_den = FIX2LONG(rb_funcall(other_ajd, id_denominator, 0));
|
360
|
+
other_num = FIX2LONG(rb_funcall(other_ajd, id_numerator, 0));
|
361
|
+
} else {
|
362
|
+
return Qnil;
|
363
|
+
}
|
364
|
+
|
365
|
+
VALUE ajd = rb_ivar_get(self, id_ivar_ajd);
|
366
|
+
long den = FIX2LONG(rb_funcall(ajd, id_denominator, 0));
|
367
|
+
long num = FIX2LONG(rb_funcall(ajd, id_numerator, 0));
|
368
|
+
|
369
|
+
long v = (num * other_den) - (other_num * den);
|
370
|
+
if (v > 0)
|
371
|
+
return INT2FIX(1);
|
372
|
+
else if (v < 0)
|
373
|
+
return INT2FIX(-1);
|
374
|
+
else
|
375
|
+
return INT2FIX(0);
|
376
|
+
}
|
377
|
+
|
378
|
+
|
379
|
+
VALUE
|
380
|
+
Init_date_performance() {
|
381
|
+
/* initialization is not idemponent - make sure it only happens once. */
|
382
|
+
if ( initialized )
|
383
|
+
rb_raise(rb_eStandardError, "date_performance extension already initialized.");
|
384
|
+
initialized = 1;
|
385
|
+
|
386
|
+
/* Grab Date class */
|
387
|
+
rb_require("date");
|
388
|
+
rb_cDate = rb_define_class("Date", rb_cObject);
|
389
|
+
|
390
|
+
if( ! rb_const_defined_from(rb_cDate, rb_intern("Performance")) )
|
391
|
+
rb_raise(rb_eStandardError,
|
392
|
+
"Date::Performance not defined. The date_performance extension can not be required directly.");
|
393
|
+
|
394
|
+
/* Date Instance Methods */
|
395
|
+
rb_define_method(rb_cDate, "civil", rb_date_civil, 0);
|
396
|
+
rb_define_method(rb_cDate, "sys_strftime", rb_date_strftime, -1);
|
397
|
+
rb_define_method(rb_cDate, "strftime", rb_date_strftime, -1);
|
398
|
+
rb_define_method(rb_cDate, "<=>", rb_date_compare, -1);
|
399
|
+
|
400
|
+
/* Date Singleton Methods */
|
401
|
+
rb_define_singleton_method(rb_cDate, "civil_to_jd", rb_date_civil_to_jd, -1);
|
402
|
+
rb_define_singleton_method(rb_cDate, "jd_to_civil", rb_date_jd_to_civil, -1);
|
403
|
+
rb_define_singleton_method(rb_cDate, "jd_to_ajd", rb_date_jd_to_ajd, -1);
|
404
|
+
rb_define_singleton_method(rb_cDate, "ajd_to_jd", rb_date_ajd_to_jd, -1);
|
405
|
+
rb_define_singleton_method(rb_cDate, "new", rb_date_new, -1);
|
406
|
+
rb_define_singleton_method(rb_cDate, "civil", rb_date_new, -1);
|
407
|
+
rb_define_singleton_method(rb_cDate, "sys_strptime", rb_date_strptime, -1);
|
408
|
+
rb_define_singleton_method(rb_cDate, "strptime", rb_date_strptime, -1);
|
409
|
+
|
410
|
+
/* Date Related Constants */
|
411
|
+
JULIAN = rb_eval_string("Date::JULIAN");
|
412
|
+
GREGORIAN = rb_eval_string("Date::GREGORIAN");
|
413
|
+
ITALY = INT2FIX(2299161);
|
414
|
+
|
415
|
+
DEFAULT_FORMAT = rb_str_new2("%F");
|
416
|
+
rb_gc_register_address(&DEFAULT_FORMAT);
|
417
|
+
|
418
|
+
/* Symbol Constants */
|
419
|
+
id_subtract = rb_intern("-");
|
420
|
+
id_add = rb_intern("+");
|
421
|
+
id_divmod = rb_intern("divmod");
|
422
|
+
id_new = rb_intern("new");
|
423
|
+
id_jd = rb_intern("jd");
|
424
|
+
id_ivar_ajd = rb_intern("@ajd");
|
425
|
+
id_jd_to_civil = rb_intern("jd_to_civil");
|
426
|
+
id_ivar_civil = rb_intern("@__civil__");
|
427
|
+
id_ivar_sg = rb_intern("@sg");
|
428
|
+
id_new_bang = rb_intern("new!");
|
429
|
+
id_numerator = rb_intern("numerator");
|
430
|
+
id_denominator = rb_intern("denominator");
|
431
|
+
id_strptime_without_performance = rb_intern("strptime_without_performance");
|
432
|
+
id_strftime_without_performance = rb_intern("strftime_without_performance");
|
433
|
+
|
434
|
+
/* Rational Stuff */
|
435
|
+
rb_require("rational");
|
436
|
+
rb_cRational = rb_define_class("Rational", rb_cNumeric);
|
437
|
+
ra_one_half = rb_funcall(rb_cRational, id_new, 2, INT2FIX(1), INT2FIX(2));
|
438
|
+
rb_gc_register_address(&ra_one_half);
|
439
|
+
return Qnil;
|
440
|
+
}
|
data/ext/extconf.rb
ADDED
data/lib/date/memoize.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# See Date::Memoize.
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
require 'date/performance'
|
5
|
+
|
6
|
+
class Date #:nodoc:
|
7
|
+
|
8
|
+
# Adds memoization to Date. This can speed things up significantly in cases where a lot
|
9
|
+
# of the same Date objects are created.
|
10
|
+
module Memoize
|
11
|
+
|
12
|
+
# Memoized version of Date::strptime.
|
13
|
+
def strptime(str='-4712-01-01', fmt='%F', sg=ITALY)
|
14
|
+
@__memoized_strptime_dates[ [ str, fmt, sg ] ]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Memoized version Date::parse.
|
18
|
+
def parse(str='-4712-01-01', comp=false, sg=ITALY)
|
19
|
+
@__memoized_parse_dates[ [ str, comp, sg ] ]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Memoized version of Date::civil.
|
23
|
+
def civil(y=-4712, m=1, d=1, sg=ITALY)
|
24
|
+
@__memoized_civil_dates[ [ y, m, d, sg ] ]
|
25
|
+
end
|
26
|
+
|
27
|
+
alias_method :new, :civil
|
28
|
+
|
29
|
+
public
|
30
|
+
|
31
|
+
# The methods we'll be replacing on the Date singleton.
|
32
|
+
def self.methods_replaced
|
33
|
+
[ :new, :civil, :strptime, :parse ]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Overridden to move the existing methods out of the way before copying this module's
|
37
|
+
# methods.
|
38
|
+
def self.extend_object(base)
|
39
|
+
singleton = (class<<base;self;end)
|
40
|
+
methods_replaced.each do |method|
|
41
|
+
singleton.send :alias_method, "#{method}_without_memoization", method
|
42
|
+
singleton.send :remove_method, method
|
43
|
+
end
|
44
|
+
base.send :instance_variable_set, :@__memoized_civil_dates,
|
45
|
+
Hash.new{|h,key| h[key]=Date.new_without_memoization(*key)}
|
46
|
+
base.send :instance_variable_set, :@__memoized_strptime_dates,
|
47
|
+
Hash.new{|h,key| h[key]=Date.strptime_without_memoization(*key)}
|
48
|
+
base.send :instance_variable_set, :@__memoized_parse_dates,
|
49
|
+
Hash.new{|h,key| h[key]=Date.parse_without_memoization(*key)}
|
50
|
+
super
|
51
|
+
end
|
52
|
+
|
53
|
+
# Removes memoization methods from singleton of the class provided.
|
54
|
+
def self.unextend_object(base)
|
55
|
+
singleton = (class<<base;self;end)
|
56
|
+
methods_replaced.each do |method|
|
57
|
+
singleton.send :alias_method, method, "#{method}_without_memoization"
|
58
|
+
singleton.send :remove_method, "#{method}_without_memoization"
|
59
|
+
end
|
60
|
+
base.send :remove_instance_variable, :@__memoized_civil_dates
|
61
|
+
base.send :remove_instance_variable, :@__memoized_strptime_dates
|
62
|
+
base.send :remove_instance_variable, :@__memoized_parse_dates
|
63
|
+
base
|
64
|
+
end
|
65
|
+
|
66
|
+
# Is Date memoization currently installed and active?
|
67
|
+
def self.installed?
|
68
|
+
Date.respond_to? :civil_without_memoization
|
69
|
+
end
|
70
|
+
|
71
|
+
# Extend the Date class with memoized versions of +new+ and +civil+ but only if
|
72
|
+
# memoization has not yet been installed.
|
73
|
+
def self.install!
|
74
|
+
Date.extend self unless installed?
|
75
|
+
end
|
76
|
+
|
77
|
+
# Remove memoized methods and free up memo cache. This method is idempotent.
|
78
|
+
def self.uninstall!
|
79
|
+
unextend_object Date if installed?
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
Memoize.install!
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
# Loading this file is not idemponent and can cause damage when loaded twice.
|
4
|
+
# Fail hard and fast.
|
5
|
+
fail "Date::Performance already loaded." if defined? Date::Performance
|
6
|
+
|
7
|
+
class Date
|
8
|
+
|
9
|
+
# The Date::Performance module is present when the performance enhacing extension
|
10
|
+
# has been loaded. It serves no other purpose.
|
11
|
+
module Performance
|
12
|
+
VERSION = "0.4.7"
|
13
|
+
end
|
14
|
+
|
15
|
+
# The extension replaces Date#strftime but falls back on the stock version when
|
16
|
+
# strftime(3) cannot handle the format.
|
17
|
+
alias_method :strftime_without_performance, :strftime
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Ruby 1.8.6 introduced Date.new! and the extension uses it. The method was
|
21
|
+
# called new0 in <= 1.8.5.
|
22
|
+
alias_method :new!, :new0 unless Date.respond_to?(:new!)
|
23
|
+
|
24
|
+
# The extension replaces Date.strptime but falls back on the stock version when
|
25
|
+
# strptime(3) can't handle the format.
|
26
|
+
alias_method :strptime_without_performance, :strptime
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
# Load up the extension but bring the Date class back to its original state
|
32
|
+
# if the extension fails to load properly.
|
33
|
+
begin
|
34
|
+
require 'date_performance.so'
|
35
|
+
rescue
|
36
|
+
class Date
|
37
|
+
remove_const :Performance
|
38
|
+
remove_method :strftime_without_performance
|
39
|
+
class << self
|
40
|
+
remove_method :strptime_without_performance
|
41
|
+
end
|
42
|
+
end
|
43
|
+
raise
|
44
|
+
end
|
data/test/benchmarks.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
$: << 'lib'
|
2
|
+
require 'date'
|
3
|
+
# require 'date/fast'
|
4
|
+
|
5
|
+
def profile_new_civil(iterations=1000)
|
6
|
+
iterations.times do |i|
|
7
|
+
Date.new(1912, 6, 23)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def profile_new_jd(iterations=1000)
|
12
|
+
jd = Date.civil_to_jd(1912, 6, 23, Date::ITALY)
|
13
|
+
iterations.times do |i|
|
14
|
+
Date.jd(jd)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def profile_comparison(iterations=1000)
|
19
|
+
d1, d2 = Date.new(1912, 6, 23), Date.new(1906, 4, 28)
|
20
|
+
iterations.times do |i|
|
21
|
+
d1 == d2
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def profile_greater_than(iterations=1000)
|
26
|
+
d1, d2 = Date.new(1912, 6, 23), Date.new(1906, 4, 28)
|
27
|
+
iterations.times do |i|
|
28
|
+
d1 > d2
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def profile_strftime(iterations=1000)
|
33
|
+
date = Date.new(1912, 6, 23)
|
34
|
+
iterations.times do |i|
|
35
|
+
date.strftime
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def profile_strftime_after_new(iterations=1000)
|
40
|
+
iterations.times do |i|
|
41
|
+
Date.new(1912, 6, 23).strftime
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def profile_strptime(iterations=1000)
|
46
|
+
iterations.times do |i|
|
47
|
+
Date.strptime '06/23/1912', '%m/%d/%Y'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def run_profiles
|
52
|
+
require 'benchmark'
|
53
|
+
all_methods = private_methods.select{|m| m =~ /^profile_/}.map{|m| m.sub /^profile_/, '' }.sort
|
54
|
+
selected_methods =
|
55
|
+
if ARGV.empty?
|
56
|
+
all_methods
|
57
|
+
else
|
58
|
+
ARGV
|
59
|
+
end
|
60
|
+
# warm-up
|
61
|
+
selected_methods.each {|method| send "profile_#{method}", 1 }
|
62
|
+
selected_methods.each do |method|
|
63
|
+
puts method
|
64
|
+
puts Benchmark.measure{ send "profile_#{method}", 10000 }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
run_profiles
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'date/performance'
|
2
|
+
require 'date/memoize'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
class DateMemoizeTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def setup
|
8
|
+
Date::Memoize.install!
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_installed_by_default
|
12
|
+
assert Date::Memoize.installed?, "didn't install when required"
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_civil_memoization
|
16
|
+
expected = Date.new_without_memoization(2002, 03, 22)
|
17
|
+
actual = Date.new(2002, 03, 22)
|
18
|
+
assert_not_same expected, actual
|
19
|
+
assert_equal expected, actual
|
20
|
+
expected, actual = actual, Date.new(2002, 03, 22)
|
21
|
+
assert_same expected, actual
|
22
|
+
assert_equal expected, actual
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_strptime_memoization
|
26
|
+
Date::Memoize.uninstall!
|
27
|
+
expected = Date.strptime('2002-03-22', '%Y-%m-%d')
|
28
|
+
Date::Memoize.install!
|
29
|
+
actual = Date.strptime('2002-03-22', '%Y-%m-%d')
|
30
|
+
assert_equal expected, actual
|
31
|
+
assert_not_same expected, actual
|
32
|
+
expected, actual = actual, Date.strptime('2002-03-22', '%Y-%m-%d')
|
33
|
+
assert_same expected, actual
|
34
|
+
assert_equal expected, actual
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_parse_memoization
|
38
|
+
Date::Memoize.uninstall!
|
39
|
+
expected = Date.parse('2002-03-22')
|
40
|
+
Date::Memoize.install!
|
41
|
+
actual = Date.parse('2002-03-22')
|
42
|
+
assert_equal expected, actual
|
43
|
+
assert_not_same expected, actual, "With (#{actual.to_s}) and without (#{expected.to_s}) memoization are the same (#{actual.object_id}, #{expected.object_id})."
|
44
|
+
expected, actual = actual, Date.parse('2002-03-22')
|
45
|
+
assert_same expected, actual
|
46
|
+
assert_equal expected, actual
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_uninstall_is_detected
|
50
|
+
Date::Memoize.uninstall!
|
51
|
+
assert !Date::Memoize.installed?, "didn't uninstall or uninstall not detected properly"
|
52
|
+
methods = Date.methods.select{|m| m.to_s =~ /_without_memoization$/}
|
53
|
+
flunk "Memoization methods not removed: #{methods.inspect}" if methods.any?
|
54
|
+
vars = Date.send(:instance_variables).select{|v| v.to_s =~ /^@__memoized_/}
|
55
|
+
flunk "Memoization instance variables not removed: #{vars.inspect}" if vars.any?
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_uninstall_removes_methods
|
59
|
+
Date::Memoize.uninstall!
|
60
|
+
methods = Date.methods.select{|m| m.to_s =~ /_without_memoization$/}
|
61
|
+
assert_equal 0, methods.length, "Memoization methods not removed: #{methods.inspect}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_uninstall_removes_instance_variables
|
65
|
+
Date::Memoize.uninstall!
|
66
|
+
vars = Date.send(:instance_variables).select{|v| v.to_s =~ /^@__memoized_/}
|
67
|
+
assert_equal 0, vars.length, "Memoization instance variables not removed: #{vars.inspect}"
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'date/performance'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
class ExtensionTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_civil_to_jd
|
7
|
+
assert_equal 2419577, Date.civil_to_jd(1912, 6, 23)
|
8
|
+
assert_equal 2419577, Date.civil_to_jd(1912, 6, 23, Date::GREGORIAN)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_jd_to_civil
|
12
|
+
expected = [ 1912, 6, 23 ]
|
13
|
+
assert_equal expected, Date.jd_to_civil(2419577)
|
14
|
+
assert_equal expected, Date.jd_to_civil(2419577, Date::GREGORIAN)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_jd_to_ajd
|
18
|
+
expected = Rational(4839153, 2)
|
19
|
+
assert_equal expected, Date.jd_to_ajd(2419577)
|
20
|
+
assert_equal expected, Date.jd_to_ajd(2419577, 0)
|
21
|
+
assert_equal expected, Date.jd_to_ajd(2419577, 0, 0)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_ajd_to_jd
|
25
|
+
ajd = Rational(4839153, 2)
|
26
|
+
expected = [ 2419577, Rational(1, 2) ]
|
27
|
+
assert_equal(expected, Date.ajd_to_jd(ajd))
|
28
|
+
assert_equal(expected, Date.ajd_to_jd(ajd, 0))
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_new
|
32
|
+
expected = Date.new!(Rational(4839153, 2))
|
33
|
+
assert_equal expected, Date.new(1912, 6, 23)
|
34
|
+
assert_equal expected, Date.new(1912, 6, 23, Date::ITALY)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_new_raises_argument_error
|
38
|
+
assert_raise ArgumentError do
|
39
|
+
Date.new(1912, 25, 55)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_civil_cached_on_new
|
44
|
+
date = Date.new(1912, 6, 23)
|
45
|
+
assert_not_nil(expected = date.send(:instance_variable_get, :@__civil__))
|
46
|
+
assert_equal expected, date.civil
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_civil_cached_on_new!
|
50
|
+
date = Date.new!(Rational(4839153, 2))
|
51
|
+
assert !date.send(:instance_variables).include?('@__civil__'), '@__civil__ ivar should not exist'
|
52
|
+
assert_equal([1912, 6, 23], date.civil)
|
53
|
+
assert_equal([1912, 6, 23], date.instance_variable_get(:@__civil__))
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_sys_strftime
|
57
|
+
date = Date.new(1912, 6, 23)
|
58
|
+
assert_equal "1912-06-23", date.sys_strftime
|
59
|
+
assert_equal "06/23/1912", date.sys_strftime("%m/%d/%Y")
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_sys_strptime
|
63
|
+
assert_equal Date.new(1912, 6, 23), Date.sys_strptime("1912-06-23")
|
64
|
+
assert_equal Date.new(1912, 6, 23), Date.sys_strptime("1912-06-23", "%Y-%m-%d")
|
65
|
+
end
|
66
|
+
|
67
|
+
# This falls back on Ruby's strptime on BSD systems because BSD's strptime doesn't
|
68
|
+
# handle years before 1900.
|
69
|
+
def test_strptime_fallback
|
70
|
+
assert_equal Date.new(1, 1, 1), Date.strptime("01/01/0001", "%m/%d/%Y")
|
71
|
+
end
|
72
|
+
|
73
|
+
# Make sure we're not breaking DateTime
|
74
|
+
def test_datetime
|
75
|
+
datetime = DateTime.parse("1912-06-23T04:20:37Z")
|
76
|
+
assert_equal 1912, datetime.year
|
77
|
+
assert_equal 6, datetime.month
|
78
|
+
assert_equal 23, datetime.day
|
79
|
+
assert_equal 4, datetime.hour
|
80
|
+
assert_equal 20, datetime.min
|
81
|
+
assert_equal 37, datetime.sec
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_new_fails_with_nils
|
85
|
+
assert_raise(TypeError) { Date.new(nil, nil, nil) }
|
86
|
+
assert_raise(TypeError) { Date.new(2007, nil, nil) }
|
87
|
+
assert_raise(TypeError) { Date.new(2007, 9, nil) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_strftime_with_weekday
|
91
|
+
assert_equal "Monday", Date.new(2007, 1, 1).strftime("%A")
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_strftime_with_datetime
|
95
|
+
dt = DateTime.new(2007, 1, 1, 4, 20, 00)
|
96
|
+
assert_equal "2007-01-01T04:20:00+00:00", dt.strftime("%FT%T%:z")
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_constructor_with_negative_days
|
100
|
+
#leap year
|
101
|
+
month_ends = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
102
|
+
(1..12).each do |m|
|
103
|
+
d = Date.new(2008, m, -1)
|
104
|
+
assert_equal d.day, month_ends[m-1]
|
105
|
+
end
|
106
|
+
#normal year
|
107
|
+
month_ends = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
108
|
+
(1..12).each do |m|
|
109
|
+
d = Date.new(2009, m, -1)
|
110
|
+
assert_equal d.day, month_ends[m-1]
|
111
|
+
end
|
112
|
+
#before calendar reform for Italy
|
113
|
+
month_ends = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
114
|
+
(1..12).each do |m|
|
115
|
+
d = Date.new(1581, m, -1)
|
116
|
+
assert_equal d.day, month_ends[m-1]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_date_comparisons
|
121
|
+
#regular Date class comparison
|
122
|
+
d = Date.new(2008, 1, 1)
|
123
|
+
366.times do |i|
|
124
|
+
assert_equal( 0, d+i <=> d+i )
|
125
|
+
assert_equal(-1, d+i <=> d+i+1 )
|
126
|
+
assert_equal( 1, d+i+1 <=> d+i )
|
127
|
+
end
|
128
|
+
|
129
|
+
#DateTime comparison
|
130
|
+
dt1 = DateTime.new(2006,1,1,12,15,30)
|
131
|
+
dt2 = DateTime.new(2006,1,1,12,15,31)
|
132
|
+
dt3 = DateTime.new(2006,1,1,12,15,29)
|
133
|
+
|
134
|
+
assert_equal( 0, DateTime.new(2006,1,1,12,15,30) <=> dt1 )
|
135
|
+
assert_equal( 1, dt1 <=> dt3 )
|
136
|
+
assert_equal( -1, dt1 <=> dt2 )
|
137
|
+
|
138
|
+
#comparison with some random type
|
139
|
+
assert_equal nil, dt1 <=> "foo"
|
140
|
+
|
141
|
+
#comparison with Fixnum that represents ajd
|
142
|
+
assert_equal(-1, d <=> d.ajd.numerator )
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: seamusabshere-date-performance
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Tomayko
|
8
|
+
- Alexander Dymo
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-05-14 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Adds some semblance of performance to Ruby's core Date class.
|
18
|
+
email: Ryan Tomayko <r@tomayko.com>
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files:
|
24
|
+
- README.md
|
25
|
+
files:
|
26
|
+
- AUTHORS
|
27
|
+
- BENCHMARKS
|
28
|
+
- README.md
|
29
|
+
- Rakefile
|
30
|
+
- VERSION.yml
|
31
|
+
- ext/date_performance.c
|
32
|
+
- ext/extconf.rb
|
33
|
+
- lib/date/memoize.rb
|
34
|
+
- lib/date/performance.rb
|
35
|
+
- test/benchmarks.rb
|
36
|
+
- test/date_memoize_test.rb
|
37
|
+
- test/extension_test.rb
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: http://tomayko.com/src/date-performance/
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options:
|
42
|
+
- --charset=UTF-8
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.2.0
|
61
|
+
signing_key:
|
62
|
+
specification_version: 3
|
63
|
+
summary: Adds some semblance of performance to Ruby's core Date class.
|
64
|
+
test_files:
|
65
|
+
- test/benchmarks.rb
|
66
|
+
- test/date_memoize_test.rb
|
67
|
+
- test/extension_test.rb
|