third_base 1.0.0
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/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