third_base 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/LICENSE +19 -0
  2. data/README +261 -0
  3. data/benchmark/date.rb +18 -0
  4. data/benchmark/datetime.rb +18 -0
  5. data/bin/third_base +4 -0
  6. data/lib/third_base/compat/date/format.rb +3 -0
  7. data/lib/third_base/compat/date.rb +3 -0
  8. data/lib/third_base/compat.rb +405 -0
  9. data/lib/third_base/date.rb +674 -0
  10. data/lib/third_base/datetime.rb +385 -0
  11. data/lib/third_base.rb +2 -0
  12. data/spec/compat/compat_class_methods_spec.rb +208 -0
  13. data/spec/compat/compat_instance_methods_spec.rb +54 -0
  14. data/spec/compat/date_spec.rb +56 -0
  15. data/spec/compat/datetime_spec.rb +77 -0
  16. data/spec/compat_spec_helper.rb +2 -0
  17. data/spec/date/accessor_spec.rb +134 -0
  18. data/spec/date/add_month_spec.rb +28 -0
  19. data/spec/date/add_spec.rb +24 -0
  20. data/spec/date/boat_spec.rb +31 -0
  21. data/spec/date/civil_spec.rb +47 -0
  22. data/spec/date/commercial_spec.rb +34 -0
  23. data/spec/date/constants_spec.rb +18 -0
  24. data/spec/date/downto_spec.rb +17 -0
  25. data/spec/date/eql_spec.rb +9 -0
  26. data/spec/date/hash_spec.rb +13 -0
  27. data/spec/date/julian_spec.rb +13 -0
  28. data/spec/date/leap_spec.rb +19 -0
  29. data/spec/date/minus_month_spec.rb +26 -0
  30. data/spec/date/minus_spec.rb +47 -0
  31. data/spec/date/ordinal_spec.rb +13 -0
  32. data/spec/date/parse_spec.rb +227 -0
  33. data/spec/date/step_spec.rb +55 -0
  34. data/spec/date/strftime_spec.rb +132 -0
  35. data/spec/date/strptime_spec.rb +118 -0
  36. data/spec/date/succ_spec.rb +16 -0
  37. data/spec/date/today_spec.rb +11 -0
  38. data/spec/date/upto_spec.rb +17 -0
  39. data/spec/date_spec_helper.rb +3 -0
  40. data/spec/datetime/accessor_spec.rb +53 -0
  41. data/spec/datetime/add_spec.rb +36 -0
  42. data/spec/datetime/boat_spec.rb +43 -0
  43. data/spec/datetime/constructor_spec.rb +58 -0
  44. data/spec/datetime/eql_spec.rb +11 -0
  45. data/spec/datetime/minus_spec.rb +65 -0
  46. data/spec/datetime/now_spec.rb +14 -0
  47. data/spec/datetime/parse_spec.rb +338 -0
  48. data/spec/datetime/strftime_spec.rb +102 -0
  49. data/spec/datetime/strptime_spec.rb +84 -0
  50. data/spec/datetime_spec_helper.rb +3 -0
  51. data/spec/spec_helper.rb +54 -0
  52. 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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['RUBYOPT'] = "-rdate #{ENV['RUBYOPT']}"
3
+ ENV['RUBYLIB'] = "#{File.join(File.dirname(File.dirname(__FILE__)), 'lib')}:#{File.join(File.dirname(File.dirname(__FILE__)), 'lib', 'third_base', 'compat')}:#{ENV['RUBYLIB']}"
4
+ exec(*ARGV)
@@ -0,0 +1,3 @@
1
+ $:.unshift(File.dirname(File.dirname(File.dirname(__FILE__))))
2
+ require 'third_base/compat'
3
+ $:.shift
@@ -0,0 +1,3 @@
1
+ $:.unshift(File.dirname(File.dirname(File.dirname(__FILE__))))
2
+ require 'third_base/compat'
3
+ $:.shift