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.
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