third_base 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README +261 -0
- data/benchmark/date.rb +18 -0
- data/benchmark/datetime.rb +18 -0
- data/bin/third_base +4 -0
- data/lib/third_base/compat/date/format.rb +3 -0
- data/lib/third_base/compat/date.rb +3 -0
- data/lib/third_base/compat.rb +405 -0
- data/lib/third_base/date.rb +674 -0
- data/lib/third_base/datetime.rb +385 -0
- data/lib/third_base.rb +2 -0
- data/spec/compat/compat_class_methods_spec.rb +208 -0
- data/spec/compat/compat_instance_methods_spec.rb +54 -0
- data/spec/compat/date_spec.rb +56 -0
- data/spec/compat/datetime_spec.rb +77 -0
- data/spec/compat_spec_helper.rb +2 -0
- data/spec/date/accessor_spec.rb +134 -0
- data/spec/date/add_month_spec.rb +28 -0
- data/spec/date/add_spec.rb +24 -0
- data/spec/date/boat_spec.rb +31 -0
- data/spec/date/civil_spec.rb +47 -0
- data/spec/date/commercial_spec.rb +34 -0
- data/spec/date/constants_spec.rb +18 -0
- data/spec/date/downto_spec.rb +17 -0
- data/spec/date/eql_spec.rb +9 -0
- data/spec/date/hash_spec.rb +13 -0
- data/spec/date/julian_spec.rb +13 -0
- data/spec/date/leap_spec.rb +19 -0
- data/spec/date/minus_month_spec.rb +26 -0
- data/spec/date/minus_spec.rb +47 -0
- data/spec/date/ordinal_spec.rb +13 -0
- data/spec/date/parse_spec.rb +227 -0
- data/spec/date/step_spec.rb +55 -0
- data/spec/date/strftime_spec.rb +132 -0
- data/spec/date/strptime_spec.rb +118 -0
- data/spec/date/succ_spec.rb +16 -0
- data/spec/date/today_spec.rb +11 -0
- data/spec/date/upto_spec.rb +17 -0
- data/spec/date_spec_helper.rb +3 -0
- data/spec/datetime/accessor_spec.rb +53 -0
- data/spec/datetime/add_spec.rb +36 -0
- data/spec/datetime/boat_spec.rb +43 -0
- data/spec/datetime/constructor_spec.rb +58 -0
- data/spec/datetime/eql_spec.rb +11 -0
- data/spec/datetime/minus_spec.rb +65 -0
- data/spec/datetime/now_spec.rb +14 -0
- data/spec/datetime/parse_spec.rb +338 -0
- data/spec/datetime/strftime_spec.rb +102 -0
- data/spec/datetime/strptime_spec.rb +84 -0
- data/spec/datetime_spec_helper.rb +3 -0
- data/spec/spec_helper.rb +54 -0
- metadata +107 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2008 Jeremy Evans <code@jeremyevans.net>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,261 @@
|
|
1
|
+
= ThirdBase: A Fast and Easy Date/DateTime Class for Ruby
|
2
|
+
|
3
|
+
ThirdBase differs from Ruby's standard Date/DateTime class in the following
|
4
|
+
ways:
|
5
|
+
|
6
|
+
- ThirdBase is roughly 2-12 times faster depending on usage
|
7
|
+
- ThirdBase has a lower memory footprint
|
8
|
+
- ThirdBase supports pluggable parsers
|
9
|
+
- ThirdBase doesn't depend on Ruby's Rational class
|
10
|
+
- ThirdBase always uses the gregorian calendar
|
11
|
+
|
12
|
+
== Background
|
13
|
+
|
14
|
+
The Ruby standard Date class tries to be all things to all people. While
|
15
|
+
it does a decent job, it's slow enough to be the bottleneck in some
|
16
|
+
applications. If we decide not to care about the Date of Calendar Reform
|
17
|
+
and the fact that the Astronomical Julian Date differs from the Julian
|
18
|
+
Date, much of the complexity of Ruby's standard Date/DateTime class can
|
19
|
+
be removed, and there can be significant improvements in speed.
|
20
|
+
|
21
|
+
== Installation
|
22
|
+
|
23
|
+
sudo gem install third_base
|
24
|
+
|
25
|
+
== Source
|
26
|
+
|
27
|
+
Source is available via github:
|
28
|
+
|
29
|
+
http://github.com/jeremyevans/third_base
|
30
|
+
|
31
|
+
You can check it out with git:
|
32
|
+
|
33
|
+
git clone git://github.com/jeremyevans/third_base.git
|
34
|
+
|
35
|
+
== Usage and Compatibility
|
36
|
+
|
37
|
+
There are three ways that ThirdBase can be used:
|
38
|
+
|
39
|
+
=== Alongside the standard Date/DateTime class
|
40
|
+
|
41
|
+
Usage:
|
42
|
+
|
43
|
+
require 'third_base'
|
44
|
+
|
45
|
+
If you just require it, you can use ThirdBase::Date and ThirdBase::DateTime
|
46
|
+
alongside the standard Date and DateTime classes. This ensures compatibility
|
47
|
+
with all existing software, but doesn't provide any performance increase to any
|
48
|
+
class not explicitly using ThirdBase.
|
49
|
+
|
50
|
+
=== Replace Date and DateTime with ThirdBase's
|
51
|
+
|
52
|
+
Usage:
|
53
|
+
|
54
|
+
require 'third_base'
|
55
|
+
include ThirdBase
|
56
|
+
|
57
|
+
This is the least compatible method. It may work for some applications but
|
58
|
+
will break most, because if they use "require 'date'", they will get a
|
59
|
+
superclass mismatch. Also ThirdBase::Date is not completely API compatible
|
60
|
+
with the standard Date class, so it could break depending on how the
|
61
|
+
application used Date.
|
62
|
+
|
63
|
+
If you aren't using any libraries that use ruby's standard Date class, this is
|
64
|
+
an easy way to be able to use Date and DateTime to refer to ThirdBase's
|
65
|
+
versions instead of Ruby's standard versions.
|
66
|
+
|
67
|
+
Note that rubygems indirectly uses the standard Date class, so if you want to
|
68
|
+
do this, you'll have to unpack the gem and put it in the $LOAD_PATH manually.
|
69
|
+
|
70
|
+
One case in which this pattern is useful is if you want to use ThirdBase within
|
71
|
+
your libraries as the date class, but with other libaries that use the standard
|
72
|
+
version as the date class. To do this:
|
73
|
+
|
74
|
+
require 'third_base'
|
75
|
+
class YourLibrary
|
76
|
+
include ThirdBase
|
77
|
+
def today
|
78
|
+
Date.today
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
This makes it so that references to Date within YourLibrary use
|
83
|
+
ThirdBase::Date, while references to Date outside YourLibrary use the standard
|
84
|
+
Date class.
|
85
|
+
|
86
|
+
=== Use ThirdBase's compatibility mode via the third_base executable
|
87
|
+
|
88
|
+
Usage:
|
89
|
+
|
90
|
+
$ third_base irb
|
91
|
+
$ third_base mongrel_rails
|
92
|
+
$ third_base ruby -rdate -e "p Date.ancestors"
|
93
|
+
|
94
|
+
This should be used if you want to make all libraries use ThirdBase's Date
|
95
|
+
class. Doing this means that even if they "require 'date'", they will use
|
96
|
+
ThirdBases's versions. More explicity, it will define Date and DateTime
|
97
|
+
as subclasses of ThirdBase::Date and ThirdBase::DateTime, and make them as
|
98
|
+
API compatible as possible.
|
99
|
+
|
100
|
+
You could get this by using "require 'third_base/compat'". Unfortunately,
|
101
|
+
that doesn't work if you are using rubygems (and ThirdBase is mainly
|
102
|
+
distributed as a gem), because rubygems indirectly requires date.
|
103
|
+
|
104
|
+
The third_base executable modifies the RUBYLIB and RUBYOPT environment
|
105
|
+
variables and should ensure that even if a ruby library requires 'date', they
|
106
|
+
will get the ThirdBase version with the compatibility API. To use the
|
107
|
+
third_base executable, you just prepend it to any command that you want to run.
|
108
|
+
|
109
|
+
This is the middle ground. It should work for most applications, but as
|
110
|
+
ThirdBase's compatibility API is not 100% compatible with the standard Date
|
111
|
+
class, things can still break. See the next section for some differences.
|
112
|
+
|
113
|
+
If you have good unit tests/specs, you can try using this in your application
|
114
|
+
then running your specs (e.g. third_base rake spec). Assuming good coverage,
|
115
|
+
if you have no errors, it should be OK to use, and you'll get a nice speedup.
|
116
|
+
|
117
|
+
== Incompatibilities with the standard Date class when using third_base/compat
|
118
|
+
|
119
|
+
* The marshalling format is different
|
120
|
+
* The new! class methods take different arguments
|
121
|
+
* Methods which returned rationals now return integers or floats
|
122
|
+
* ajd and amjd are now considered the same as jd and mjd, respectively
|
123
|
+
* The gregorian calendar is now the only calendar used
|
124
|
+
* All parsed two digit years are mapped to a year between 1969 and 2068
|
125
|
+
* Default parsing may be different, but the user can modify the parsers used
|
126
|
+
* Potentially others, but hopefully anything else can be fixed
|
127
|
+
|
128
|
+
== Pluggable Parsers
|
129
|
+
|
130
|
+
The standard Date class has a hard coded parsing routine that cannot be easily
|
131
|
+
modified by the user. ThirdBase uses a different approach, by allowing the
|
132
|
+
user to add parsers and change the order of parsers. There are some default
|
133
|
+
parsers built into ThirdBase's Date and DateTime, and they should work well for
|
134
|
+
the majority of American users. However, there is no guarantee that it
|
135
|
+
includes a parser for the format you want to parse (though you can add a parser
|
136
|
+
that will do so).
|
137
|
+
|
138
|
+
The user should note that ThirdBases's Date and DateTime classes have
|
139
|
+
completely separate parsers, and modifying one does not affect the other.
|
140
|
+
|
141
|
+
=== Adding Parser Types
|
142
|
+
|
143
|
+
ThirdBase's parsers are separated into parser types. The Date class has
|
144
|
+
four parser types built in: :iso, :us, :num, and :eu, of which only :iso,
|
145
|
+
:us, and :num are used by default. DateTime has all of the parser types
|
146
|
+
that Date has, and an additional one called :time.
|
147
|
+
|
148
|
+
To add a parser type:
|
149
|
+
|
150
|
+
Date.add_parser_type(:mine)
|
151
|
+
DateTime.add_parser_type(:mine)
|
152
|
+
|
153
|
+
=== Adding Parsers to Parser Types
|
154
|
+
|
155
|
+
A ThirdBase Date/Datetime parser consists of two parts, a regular
|
156
|
+
expression, and a proc that takes a MatchData object and returns a hash
|
157
|
+
passed to Date/DateTime.new!. The proc is only called if the regular
|
158
|
+
expression matches the string to be parsed, and it can return nil if it
|
159
|
+
is not able to successfully parse the string (even if the string matches
|
160
|
+
the regular expression). To add a parser, you use the add_parser class
|
161
|
+
method, which takes an argument specifying which parser family to
|
162
|
+
use, the regular expression, and a block that is used as a proc for the
|
163
|
+
parser:
|
164
|
+
|
165
|
+
To add a parser to a parser type:
|
166
|
+
|
167
|
+
Date.add_parser(:mine, /\Atoday\z/i) do |m|
|
168
|
+
t = Time.now
|
169
|
+
{:civil=>[t.year, t.mon, t.day]}
|
170
|
+
end
|
171
|
+
DateTime.add_parser(:mine, /\Anow\z/i) do |m|
|
172
|
+
t = Time.now
|
173
|
+
{:civil=>[t.year, t.mon, t.day], :parts=>[t.hour, t.min, t.sec, t.usec] \
|
174
|
+
:offset=>t.utc_offset}
|
175
|
+
end
|
176
|
+
|
177
|
+
Adding a parser to a parser type adds it to the front of the array of parsers
|
178
|
+
for that type, so it will be tried before other parsers for that type. It is
|
179
|
+
an error to add a parser to a parser type that doesn't exist.
|
180
|
+
|
181
|
+
=== Modifying the Order of Parsers Types
|
182
|
+
|
183
|
+
You can change the order in which parsers types are tried by using the
|
184
|
+
use_parsers class method, which takes multiple arguments specifying the order
|
185
|
+
of parser types:
|
186
|
+
|
187
|
+
To modify the order of parser types:
|
188
|
+
|
189
|
+
Date.use_parsers(:mine, :num, :iso, :us)
|
190
|
+
DateTime.use_parsers(:time, :iso, :mine, :eu, :num)
|
191
|
+
|
192
|
+
== Performance
|
193
|
+
|
194
|
+
=== Synthetic Benchmark
|
195
|
+
|
196
|
+
Date vs. ThirdBase::Date: 20000 Iterations
|
197
|
+
user system total real
|
198
|
+
Date.new 1.210000 0.000000 1.210000 ( 1.209048)
|
199
|
+
ThirdBase::Date.new 0.240000 0.000000 0.240000 ( 0.237548)
|
200
|
+
Date.new >> 4.100000 0.010000 4.110000 ( 4.107972)
|
201
|
+
ThirdBase::Date.new >> 0.580000 0.010000 0.590000 ( 0.585797)
|
202
|
+
Date.new + 1.580000 0.030000 1.610000 ( 1.613447)
|
203
|
+
ThirdBase::Date.new + 0.810000 0.000000 0.810000 ( 0.803092)
|
204
|
+
Date.parse 6.180000 0.180000 6.360000 ( 6.364501)
|
205
|
+
ThirdBase::Date.parse 0.540000 0.000000 0.540000 ( 0.532560)
|
206
|
+
Date.strptime 6.680000 0.030000 6.710000 ( 6.707893)
|
207
|
+
ThirdBase::Date.strptime 2.200000 0.040000 2.240000 ( 2.241585)
|
208
|
+
DateTime vs. ThirdBase::DateTime: 20000 Iterations
|
209
|
+
user system total real
|
210
|
+
DateTime.new 3.490000 0.270000 3.760000 ( 3.760513)
|
211
|
+
ThirdBase::DateTime.new 0.350000 0.000000 0.350000 ( 0.357525)
|
212
|
+
DateTime.new >> 6.720000 0.230000 6.950000 ( 6.953825)
|
213
|
+
ThirdBase::DateTime.new >> 0.840000 0.020000 0.860000 ( 0.854347)
|
214
|
+
DateTime.new + 3.730000 0.170000 3.900000 ( 3.894309)
|
215
|
+
ThirdBase::DateTime.new + 0.780000 0.060000 0.840000 ( 0.834865)
|
216
|
+
DateTime.parse 8.450000 0.400000 8.850000 ( 8.854514)
|
217
|
+
ThirdBase::DateTime.parse 0.980000 0.040000 1.020000 ( 1.015109)
|
218
|
+
DateTime.strptime 10.860000 0.380000 11.240000 ( 11.243913)
|
219
|
+
ThirdBase::DateTime.strptime 3.410000 0.160000 3.570000 ( 3.574491)
|
220
|
+
|
221
|
+
=== Real World Example
|
222
|
+
|
223
|
+
ThirdBase was written to solve a real world problem, slow retreval of records
|
224
|
+
from a database because they contained many date fields. The table in
|
225
|
+
question (employees), has 23 fields, 5 of which are date fields. Here are
|
226
|
+
the results of selecting all records for the database via Sequel, both with
|
227
|
+
and without third_base:
|
228
|
+
|
229
|
+
$ script/benchmarker 100 Employee.all
|
230
|
+
user system total real
|
231
|
+
#1 25.990000 0.040000 26.030000 ( 27.587781)
|
232
|
+
$ third_base script/benchmarker 100 Employee.all
|
233
|
+
user system total real
|
234
|
+
#1 13.640000 0.100000 13.740000 ( 15.018741)
|
235
|
+
|
236
|
+
Note that the times above include the time to query the database and
|
237
|
+
instantiate all of the Model objects. In this instance you can see that
|
238
|
+
ThirdBase doubles performance with no change to the existing code. This is
|
239
|
+
do to the fact that previously, date-related code took about 3/4 of the
|
240
|
+
processing time:
|
241
|
+
|
242
|
+
ruby-prof graph profile without ThirdBase for Employee.all 100 times:
|
243
|
+
|
244
|
+
75.87% 1.05% 101.51 1.40 0.00 100.12 85500 <Class::Date>#new (/usr/local/lib/ruby/1.8/date.rb:725}
|
245
|
+
|
246
|
+
ruby-prof graph profile with ThirdBase for Employee.all 100 times:
|
247
|
+
|
248
|
+
36.43% 1.29% 18.01 0.64 0.00 17.37 85500 <Class::ThirdBase::Date>#new
|
249
|
+
|
250
|
+
ThirdBase still takes up over a third of the processing time, but the total
|
251
|
+
time it takes has been reduced by a factor of 5. There may be opportunities
|
252
|
+
to further speed up ThirdBase--while it was designed to be faster than the
|
253
|
+
default Date class, there have been no attempts to optimize its performance.
|
254
|
+
|
255
|
+
== License
|
256
|
+
|
257
|
+
ThirdBase is released under the MIT License. See the LICENSE file for details.
|
258
|
+
|
259
|
+
== Author
|
260
|
+
|
261
|
+
Jeremy Evans <code@jeremyevans.net>
|
data/benchmark/date.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'benchmark'
|
3
|
+
require 'date'
|
4
|
+
require 'third_base/date'
|
5
|
+
n = 20000
|
6
|
+
puts "Date vs. ThirdBase::Date: #{n} Iterations"
|
7
|
+
Benchmark.bm do |x|
|
8
|
+
GC.start; x.report("Date.new "){n.times{Date.new(2008, 1, 1)}}
|
9
|
+
GC.start; x.report("ThirdBase::Date.new "){n.times{ThirdBase::Date.new(2008, 1, 1)}}
|
10
|
+
GC.start; x.report("Date.new >> "){n.times{Date.new(2008, 1, 1)>>3}}
|
11
|
+
GC.start; x.report("ThirdBase::Date.new >> "){n.times{ThirdBase::Date.new(2008, 1, 1)>>3}}
|
12
|
+
GC.start; x.report("Date.new + "){n.times{Date.new(2008, 1, 1)+3}}
|
13
|
+
GC.start; x.report("ThirdBase::Date.new + "){n.times{ThirdBase::Date.new(2008, 1, 1)+3}}
|
14
|
+
GC.start; x.report("Date.parse "){n.times{Date.parse("2008-01-01")}}
|
15
|
+
GC.start; x.report("ThirdBase::Date.parse "){n.times{ThirdBase::Date.parse("2008-01-01")}}
|
16
|
+
GC.start; x.report("Date.strptime "){n.times{Date.strptime("2008-01-01", "%Y-%m-%d")}}
|
17
|
+
GC.start; x.report("ThirdBase::Date.strptime"){n.times{ThirdBase::Date.strptime("2008-01-01", "%Y-%m-%d")}}
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'benchmark'
|
3
|
+
require 'date'
|
4
|
+
require 'third_base/datetime'
|
5
|
+
n = 20000
|
6
|
+
puts "DateTime vs. ThirdBase::DateTime: #{n} Iterations"
|
7
|
+
Benchmark.bm do |x|
|
8
|
+
GC.start; x.report("DateTime.new "){n.times{DateTime.new(2008, 1, 1, 10, 15, 16)}}
|
9
|
+
GC.start; x.report("ThirdBase::DateTime.new "){n.times{ThirdBase::DateTime.new(2008, 1, 1, 10, 15, 16)}}
|
10
|
+
GC.start; x.report("DateTime.new >> "){n.times{DateTime.new(2008, 1, 1, 10, 15, 16)>>3}}
|
11
|
+
GC.start; x.report("ThirdBase::DateTime.new >> "){n.times{ThirdBase::DateTime.new(2008, 1, 1, 10, 15, 16)>>3}}
|
12
|
+
GC.start; x.report("DateTime.new + "){n.times{DateTime.new(2008, 1, 1, 10, 15, 16)+3.5}}
|
13
|
+
GC.start; x.report("ThirdBase::DateTime.new + "){n.times{ThirdBase::DateTime.new(2008, 1, 1, 10, 15, 16)+3.5}}
|
14
|
+
GC.start; x.report("DateTime.parse "){n.times{DateTime.parse("2008-01-01 10:15:16")}}
|
15
|
+
GC.start; x.report("ThirdBase::DateTime.parse "){n.times{ThirdBase::DateTime.parse("2008-01-01 10:15:16")}}
|
16
|
+
GC.start; x.report("DateTime.strptime "){n.times{DateTime.strptime("2008-01-01 10:15:16", "%F %T")}}
|
17
|
+
GC.start; x.report("ThirdBase::DateTime.strptime"){n.times{ThirdBase::DateTime.strptime("2008-01-01 10:15:16", "%F %T")}}
|
18
|
+
end
|
data/bin/third_base
ADDED