delocalize 0.1.3

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.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,117 @@
1
+ delocalize
2
+ ==========
3
+
4
+ Highly experimental date/time and number parsing. Use at your own risk! ;-)
5
+
6
+ Installation
7
+ ============
8
+
9
+ You can use delocalize either as a gem (preferred) or as a Rails plugin.
10
+
11
+ To use the gem version, put the following gem requirement in your environment.rb:
12
+
13
+ config.gem "delocalize", :source => 'http://gemcutter.org'
14
+
15
+ To install it as a plugin, fire up your terminal, go to your Rails app and type:
16
+
17
+ $ ruby script/plugin install git://github.com/clemens/delocalize.git
18
+
19
+
20
+ What does it do? And how do I use it?
21
+ =====================================
22
+
23
+ Delocalize, just as the name suggest, does pretty much the opposite of localize.
24
+
25
+ In the grey past, ff you want your users to be able to input localized data, such as dates and numbers, you had to manually override attribute accessors:
26
+
27
+ def price=(price)
28
+ write_attribute(:price, price.gsub(',', '.'))
29
+ end
30
+
31
+ delocalize does this under the covers - all you need is your regular translation data (as YAML or Ruby file) where you need Rails' standard translations:
32
+
33
+ de:
34
+ number:
35
+ format:
36
+ separator: ','
37
+ delimiter: '.'
38
+ date:
39
+ input:
40
+ formats: [:default, :long, :short] # <- this and ...
41
+
42
+ formats:
43
+ default: "%d.%m.%Y"
44
+ short: "%e. %b"
45
+ long: "%e. %B %Y"
46
+ only_day: "%e"
47
+
48
+ day_names: [Sonntag, Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag]
49
+ abbr_day_names: [So, Mo, Di, Mi, Do, Fr, Sa]
50
+ month_names: [~, Januar, Februar, März, April, Mai, Juni, Juli, August, September, Oktober, November, Dezember]
51
+ abbr_month_names: [~, Jan, Feb, Mär, Apr, Mai, Jun, Jul, Aug, Sep, Okt, Nov, Dez]
52
+ order: [ :day, :month, :year ]
53
+
54
+ time:
55
+ input:
56
+ formats: [:long, :medium, :short, :default, :time] # <- ... this are the only non-standard keys
57
+ formats:
58
+ default: "%A, %e. %B %Y, %H:%M Uhr"
59
+ short: "%e. %B, %H:%M Uhr"
60
+ long: "%A, %e. %B %Y, %H:%M Uhr"
61
+ time: "%H:%M"
62
+
63
+ am: "vormittags"
64
+ pm: "nachmittags"
65
+
66
+ For dates and times, you have to define input formats which are taken from the actual formats. The important thing here is to define input formats sorted by descending complexity - in other words: The format which contains the most (preferably non-numeric) information should be first in the list because it can produce the most reliable match. Exception: If you think there most complex format is not the one that most users will input, you can put the most-used in front so you save unnecessary iterations.
67
+
68
+ Careful with formats containing only numbers: It's very hard to produce reliable matches if you provide multiple strictly numeric formats!
69
+
70
+ delocalize then overrides to_input_field_tag in ActionView's InstanceTag so you can use localized text fields:
71
+
72
+ <% form_for @product do |f| %>
73
+ <%= f.text_field :name %>
74
+ <%= f.text_field :released_on %>
75
+ <%= f.text_field :price %>
76
+ <% end %>
77
+
78
+ In this example, a user can enter the release date and the price just like he's used to in his language, for example:
79
+
80
+ Name: "Couch"
81
+ Released on: "12. Oktober 2009"
82
+ Price: "2.999,90"
83
+
84
+ When saved, ActiveRecord automatically converts these to a regular Ruby date and number.
85
+
86
+ Edit forms then also show localized dates/numbers. By default, dates and times are localized using the format named :default in your locale file. So with the above locale file, dates would use "%d.%m.%Y" and times would use "%A, %e. %B %Y, %H:%M Uhr". Numbers are also formatted using your locale's thousands delimiter and decimal separator.
87
+
88
+ You can also customize the output using some options:
89
+
90
+ The price should always show two decimal digits and we don't need the delimiter:
91
+ <%= f.text_field :price, :precision => 2, :delimiter => '' %>
92
+
93
+ The released_on date should be shown in the :full format:
94
+ <%= f.text_field :released_on, :format => :full %>
95
+
96
+ Since I18n.localize supportes localizing strftime strings, we can also do this:
97
+ <%= f.text_field :released_on, :format => "%B %Y" %>
98
+
99
+ Note
100
+ ====
101
+
102
+ delocalize is most definitely not enterprise-ready! ;-)
103
+ Or as Yaroslav says: Contains small pieces. Not good for children of age 3 and less. Not enterprise-ready.
104
+
105
+ TODO
106
+ ====
107
+
108
+ * Improve test coverage
109
+ * Separate Ruby/Rails stuff to make it usable outside Rails
110
+ * Verify correct behavior with time zones
111
+ * Decide on other ActionView hacks (e.g. text_field_tag)
112
+ * Implement AM/PM support
113
+ * Cleanup, cleanup, cleanup ...
114
+
115
+ Copyright (c) 2009 Clemens Kofler <clemens@railway.at>
116
+ www.railway.at
117
+ Released under the MIT license
@@ -0,0 +1,10 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ require 'tasks/distribution'
6
+ require 'tasks/documentation'
7
+ require 'tasks/testing'
8
+
9
+ desc 'Default: run unit tests.'
10
+ task :default => :test
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.3
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'delocalize'
@@ -0,0 +1,4 @@
1
+ require 'delocalize/ruby_ext'
2
+ require 'delocalize/i18n_ext'
3
+ require 'delocalize/rails_ext'
4
+ require 'delocalize/localized_date_time_parser'
@@ -0,0 +1,28 @@
1
+ module I18n
2
+ mattr_accessor :enable_delocalization
3
+ I18n.enable_delocalization = true
4
+
5
+ class << self
6
+ def delocalization_enabled?
7
+ !!I18n.enable_delocalization
8
+ end
9
+
10
+ def delocalization_disabled?
11
+ !delocalization_enabled?
12
+ end
13
+
14
+ def with_delocalization_disabled(&block)
15
+ old_value = I18n.enable_delocalization
16
+ I18n.enable_delocalization = false
17
+ yield
18
+ I18n.enable_delocalization = old_value
19
+ end
20
+
21
+ def with_delocalization_enabled(&block)
22
+ old_value = I18n.enable_delocalization
23
+ I18n.enable_delocalization = true
24
+ yield
25
+ I18n.enable_delocalization = old_value
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,68 @@
1
+ # TODO:
2
+ # * AM/PM calculation
3
+ # * proper documentation (comments)
4
+ module Delocalize
5
+ class LocalizedDateTimeParser
6
+ class << self
7
+ def parse(datetime, type)
8
+ return unless datetime
9
+ return datetime if datetime.respond_to?(:strftime) # already a Date/Time object -> no need to parse it
10
+
11
+ translate_month_and_day_names(datetime)
12
+ input_formats(type).each do |original_format|
13
+ next unless datetime =~ /^#{apply_regex(original_format)}$/
14
+
15
+ datetime = DateTime.strptime(datetime, original_format)
16
+ return Date == type ?
17
+ datetime.to_date :
18
+ Time.zone.local(datetime.year, datetime.mon, datetime.mday, datetime.hour, datetime.min, datetime.sec)
19
+ end
20
+ default_parse(datetime, type)
21
+ end
22
+
23
+ private
24
+ def default_parse(datetime, type)
25
+ return if datetime.blank?
26
+ begin
27
+ today = Date.current
28
+ parsed = Date._parse(datetime)
29
+ return if parsed.empty? # the datetime value is invalid
30
+ # set default year, month and day if not found
31
+ parsed.reverse_merge!(:year => today.year, :mon => today.mon, :mday => today.mday)
32
+ datetime = Time.zone.local(*parsed.values_at(:year, :mon, :mday, :hour, :min, :sec))
33
+ Date == type ? datetime.to_date : datetime
34
+ rescue
35
+ datetime
36
+ end
37
+ end
38
+
39
+ def translate_month_and_day_names(datetime)
40
+ translated = I18n.t([:month_names, :abbr_month_names, :day_names, :abbr_day_names], :scope => :date).flatten.compact
41
+ original = (Date::MONTHNAMES + Date::ABBR_MONTHNAMES + Date::DAYNAMES + Date::ABBR_DAYNAMES).compact
42
+ translated.each_with_index { |name, i| datetime.gsub!(name, original[i]) }
43
+ end
44
+
45
+ def input_formats(type)
46
+ # Date uses date formats, all others use time formats
47
+ type = type == Date ? :date : :time
48
+ (@input_formats ||= {})[type] ||= I18n.t(:"#{type}.formats").slice(*I18n.t(:"#{type}.input.formats")).values
49
+ end
50
+
51
+ def apply_regex(format)
52
+ # maybe add other options as well
53
+ format.gsub('%B', "(#{Date::MONTHNAMES.compact.join('|')})"). # long month name
54
+ gsub('%b', "(#{Date::ABBR_MONTHNAMES.compact.join('|')})"). # short month name
55
+ gsub('%m', "(\\d{2})"). # numeric month
56
+ gsub('%A', "(#{Date::DAYNAMES.join('|')})"). # full day name
57
+ gsub('%a', "(#{Date::ABBR_DAYNAMES.join('|')})"). # short day name
58
+ gsub('%Y', "(\\d{4})"). # long year
59
+ gsub('%y', "(\\d{2})"). # short year
60
+ gsub('%e', "(\\w?\\d{1,2})"). # short day
61
+ gsub('%d', "(\\d{2})"). # full day
62
+ gsub('%H', "(\\d{2})"). # hour (24)
63
+ gsub('%M', "(\\d{2})"). # minute
64
+ gsub('%S', "(\\d{2})") # second
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,3 @@
1
+ require 'delocalize/rails_ext/active_record'
2
+ require 'delocalize/rails_ext/action_view'
3
+ require 'delocalize/rails_ext/time_zone'
@@ -0,0 +1,48 @@
1
+ # TODO: also override other methods like to_check_box_tag since they might contain numeric values?
2
+ # ActionView needs some patching too
3
+
4
+ ActionView::Helpers::InstanceTag.class_eval do
5
+ include ActionView::Helpers::NumberHelper
6
+
7
+ alias original_to_input_field_tag to_input_field_tag
8
+ def to_input_field_tag(field_type, options = {})
9
+ options.symbolize_keys!
10
+ # numbers and dates/times should be localized unless value is already defined
11
+ if object && options[:value].blank? && object.respond_to?(:column_for_attribute) && column = object.column_for_attribute(method_name)
12
+ # a little verbose
13
+ if column.number? || column.date? || column.time?
14
+ value = object.send(method_name)
15
+
16
+ if column.number?
17
+ number_options = I18n.t(:'number.format')
18
+ separator = options.delete(:separator) || number_options[:separator]
19
+ delimiter = options.delete(:delimiter) || number_options[:delimiter]
20
+ precision = options.delete(:precision) || number_options[:precision]
21
+ opts = { :separator => separator, :delimiter => delimiter, :precision => precision }
22
+ # integers don't need a precision
23
+ opts.merge!(:precision => 0) if column.type == :integer
24
+
25
+ options[:value] = number_with_precision(value, opts)
26
+ elsif column.date? || column.time?
27
+ options[:value] = value ? I18n.l(value, :format => options.delete(:format)) : nil
28
+ end
29
+ end
30
+ end
31
+
32
+ original_to_input_field_tag(field_type, options)
33
+ end
34
+ end
35
+
36
+ # TODO: does it make sense to also override FormTagHelper methods?
37
+ # ActionView::Helpers::FormTagHelper.class_eval do
38
+ # include ActionView::Helpers::NumberHelper
39
+ #
40
+ # alias original_text_field_tag text_field_tag
41
+ # def text_field_tag(name, value = nil, options = {})
42
+ # value = options.delete(:value) if options.key?(:value)
43
+ # if value.is_a?(Numeric)
44
+ # value = number_with_delimiter(value)
45
+ # end
46
+ # original_text_field_tag(name, value, options)
47
+ # end
48
+ # end
@@ -0,0 +1,47 @@
1
+ # let's hack into ActiveRecord a bit - everything at the lowest possible level, of course, so we minimalize side effects
2
+ ActiveRecord::ConnectionAdapters::Column.class_eval do
3
+ def date?
4
+ klass == Date
5
+ end
6
+
7
+ def time?
8
+ klass == Time
9
+ end
10
+ end
11
+
12
+ ActiveRecord::Base.class_eval do
13
+ def write_attribute_with_localization(attr_name, value)
14
+ if column = column_for_attribute(attr_name.to_s)
15
+ if column.date?
16
+ value = Date.parse_localized(value)
17
+ elsif column.time?
18
+ value = Time.parse_localized(value)
19
+ end
20
+ end
21
+ write_attribute_without_localization(attr_name, value)
22
+ end
23
+ alias_method_chain :write_attribute, :localization
24
+
25
+ # ugh
26
+ def self.define_write_method_for_time_zone_conversion(attr_name)
27
+ method_body = <<-EOV
28
+ def #{attr_name}=(time)
29
+ unless time.acts_like?(:time)
30
+ time = time.is_a?(String) ? (I18n.delocalization_enabled? ? Time.zone.parse_localized(time) : Time.zone.parse(time)) : time.to_time rescue time
31
+ end
32
+ time = time.in_time_zone rescue nil if time
33
+ write_attribute(:#{attr_name}, time)
34
+ end
35
+ EOV
36
+ evaluate_attribute_method attr_name, method_body, "#{attr_name}="
37
+ end
38
+
39
+ def convert_number_column_value_with_localization(value)
40
+ value = convert_number_column_value_without_localization(value)
41
+ if I18n.delocalization_enabled? && value.is_a?(String)
42
+ value = value.gsub(/[^0-9\-#{I18n.t(:'number.format.separator')}]/, '').gsub(I18n.t(:'number.format.separator'), '.')
43
+ end
44
+ value
45
+ end
46
+ alias_method_chain :convert_number_column_value, :localization
47
+ end
@@ -0,0 +1,7 @@
1
+ require 'delocalize/localized_date_time_parser'
2
+
3
+ ActiveSupport::TimeZone.class_eval do
4
+ def parse_localized(time_with_zone)
5
+ Delocalize::LocalizedDateTimeParser.parse(time_with_zone, self.class)
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ require 'delocalize/ruby_ext/date'
2
+ require 'delocalize/ruby_ext/datetime'
3
+ require 'delocalize/ruby_ext/time'
@@ -0,0 +1,9 @@
1
+ require 'delocalize/localized_date_time_parser'
2
+
3
+ Date.class_eval do
4
+ class << self
5
+ def parse_localized(date)
6
+ Delocalize::LocalizedDateTimeParser.parse(date, self)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'delocalize/localized_date_time_parser'
2
+
3
+ DateTime.class_eval do
4
+ class << self
5
+ def parse_localized(datetime)
6
+ Delocalize::LocalizedDateTimeParser.parse(datetime, self)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'delocalize/localized_date_time_parser'
2
+
3
+ Time.class_eval do
4
+ class << self
5
+ def parse_localized(time)
6
+ Delocalize::LocalizedDateTimeParser.parse(time, self)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |s|
4
+ s.name = "delocalize"
5
+ s.summary = "Localized date/time and number parsing"
6
+ s.email = "clemens@railway.at"
7
+ s.homepage = "http://github.com/clemens/delocalize"
8
+ s.description = "Delocalize is a tool for parsing localized dates/times and numbers."
9
+ s.authors = ["Clemens Kofler"]
10
+ s.files = FileList["init.rb",
11
+ "lib/**/*.rb",
12
+ "MIT-LICENSE",
13
+ "Rakefile",
14
+ "README",
15
+ "tasks/**/*.rb",
16
+ "VERSION"]
17
+ s.test_files = FileList["test/**/*.rb"]
18
+ end
19
+ rescue LoadError
20
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
21
+ end
@@ -0,0 +1,8 @@
1
+ desc 'Generate documentation for the delocalize plugin.'
2
+ Rake::RDocTask.new(:rdoc) do |rdoc|
3
+ rdoc.rdoc_dir = 'rdoc'
4
+ rdoc.title = 'Delocalize'
5
+ rdoc.options << '--line-numbers' << '--inline-source'
6
+ rdoc.rdoc_files.include('README')
7
+ rdoc.rdoc_files.include('lib/**/*.rb')
8
+ end
@@ -0,0 +1,7 @@
1
+ desc 'Test the delocalize plugin.'
2
+ Rake::TestTask.new(:test) do |t|
3
+ t.libs << 'lib'
4
+ t.libs << 'test'
5
+ t.pattern = 'test/**/*_test.rb'
6
+ t.verbose = true
7
+ end
@@ -0,0 +1,208 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class DelocalizeActiveRecordTest < ActiveRecord::TestCase
4
+ def setup
5
+ Time.zone = 'Berlin' # make sure everything works as expected with TimeWithZone
6
+ @product = Product.new
7
+ end
8
+
9
+ test "delocalizes localized number" do
10
+ @product.price = '1.299,99'
11
+ assert_equal 1299.99, @product.price
12
+
13
+ @product.price = '-1.299,99'
14
+ assert_equal -1299.99, @product.price
15
+ end
16
+
17
+ test "delocalizes localized date" do
18
+ date = Date.civil(2009, 10, 19)
19
+
20
+ @product.released_on = '19. Oktober 2009'
21
+ assert_equal date, @product.released_on
22
+
23
+ @product.released_on = '19. Okt'
24
+ assert_equal date, @product.released_on
25
+
26
+ @product.released_on = '19.10.2009'
27
+ assert_equal date, @product.released_on
28
+ end
29
+
30
+ test "delocalizes localized datetime" do
31
+ time = Time.local(2009, 3, 1, 12, 0, 0)
32
+
33
+ @product.published_at = 'Sonntag, 1. März 2009, 12:00 Uhr'
34
+ assert_equal time, @product.published_at
35
+
36
+ @product.published_at = '1. März 2009, 12:00 Uhr'
37
+ assert_equal time, @product.published_at
38
+
39
+ @product.published_at = '1. März, 12:00 Uhr'
40
+ assert_equal time, @product.published_at
41
+ end
42
+
43
+ test "delocalizes localized time" do
44
+ now = Time.current
45
+ time = Time.local(now.year, now.month, now.day, 9, 0, 0)
46
+ @product.cant_think_of_a_sensible_time_field = '09:00 Uhr'
47
+ assert_equal time, @product.cant_think_of_a_sensible_time_field
48
+ end
49
+
50
+ test "uses default parse if format isn't found" do
51
+ date = Date.civil(2009, 10, 19)
52
+
53
+ @product.released_on = '2009/10/19'
54
+ assert_equal date, @product.released_on
55
+
56
+ time = Time.local(2009, 3, 1, 12, 0, 0)
57
+ @product.published_at = '2009/03/01 12:00'
58
+ assert_equal time, @product.published_at
59
+
60
+ now = Time.current
61
+ time = Time.local(now.year, now.month, now.day, 9, 0, 0)
62
+ @product.cant_think_of_a_sensible_time_field = '09:00'
63
+ assert_equal time, @product.cant_think_of_a_sensible_time_field
64
+ end
65
+
66
+ test "should return nil if the input is empty or invalid" do
67
+ @product.released_on = ""
68
+ assert_nil @product.released_on
69
+
70
+ @product.released_on = "aa"
71
+ assert_nil @product.released_on
72
+ end
73
+
74
+ test "doesn't raise when attribute is nil" do
75
+ assert_nothing_raised {
76
+ @product.price = nil
77
+ @product.released_on = nil
78
+ @product.published_at = nil
79
+ @product.cant_think_of_a_sensible_time_field = nil
80
+ }
81
+ end
82
+
83
+ test "uses default formats if enable_delocalization is false" do
84
+ I18n.enable_delocalization = false
85
+
86
+ @product.price = '1299.99'
87
+ assert_equal 1299.99, @product.price
88
+
89
+ @product.price = '-1299.99'
90
+ assert_equal -1299.99, @product.price
91
+ end
92
+
93
+ test "uses default formats if called with with_delocalization_disabled" do
94
+ I18n.with_delocalization_disabled do
95
+ @product.price = '1299.99'
96
+ assert_equal 1299.99, @product.price
97
+
98
+ @product.price = '-1299.99'
99
+ assert_equal -1299.99, @product.price
100
+ end
101
+ end
102
+
103
+ test "uses localized parsing if called with with_delocalization_enabled" do
104
+ I18n.with_delocalization_enabled do
105
+ @product.price = '1.299,99'
106
+ assert_equal 1299.99, @product.price
107
+
108
+ @product.price = '-1.299,99'
109
+ assert_equal -1299.99, @product.price
110
+ end
111
+ end
112
+ end
113
+
114
+ class DelocalizeActionViewTest < ActionView::TestCase
115
+ include ActionView::Helpers::FormHelper
116
+
117
+ def setup
118
+ Time.zone = 'Berlin' # make sure everything works as expected with TimeWithZone
119
+ @product = Product.new
120
+ end
121
+
122
+ test "shows text field using formatted number" do
123
+ @product.price = 1299.9
124
+ assert_dom_equal '<input id="product_price" name="product[price]" size="30" type="text" value="1.299,90" />',
125
+ text_field(:product, :price)
126
+ end
127
+
128
+ test "shows text field using formatted number with options" do
129
+ @product.price = 1299.995
130
+ assert_dom_equal '<input id="product_price" name="product[price]" size="30" type="text" value="1,299.995" />',
131
+ text_field(:product, :price, :precision => 3, :delimiter => ',', :separator => '.')
132
+ end
133
+
134
+ test "shows text field using formatted number without precision if column is an integer" do
135
+ @product.times_sold = 20
136
+ assert_dom_equal '<input id="product_times_sold" name="product[times_sold]" size="30" type="text" value="20" />',
137
+ text_field(:product, :times_sold)
138
+
139
+ @product.times_sold = 2000
140
+ assert_dom_equal '<input id="product_times_sold" name="product[times_sold]" size="30" type="text" value="2.000" />',
141
+ text_field(:product, :times_sold)
142
+ end
143
+
144
+ test "shows text field using formatted date" do
145
+ @product.released_on = Date.civil(2009, 10, 19)
146
+ assert_dom_equal '<input id="product_released_on" name="product[released_on]" size="30" type="text" value="19.10.2009" />',
147
+ text_field(:product, :released_on)
148
+ end
149
+
150
+ test "shows text field using formatted date and time" do
151
+ @product.published_at = Time.local(2009, 3, 1, 12, 0, 0)
152
+ # careful - leading whitespace with %e
153
+ assert_dom_equal '<input id="product_published_at" name="product[published_at]" size="30" type="text" value="Sonntag, 1. März 2009, 12:00 Uhr" />',
154
+ text_field(:product, :published_at)
155
+ end
156
+
157
+ test "shows text field using formatted date with format" do
158
+ @product.released_on = Date.civil(2009, 10, 19)
159
+ assert_dom_equal '<input id="product_released_on" name="product[released_on]" size="30" type="text" value="19. Oktober 2009" />',
160
+ text_field(:product, :released_on, :format => :long)
161
+ end
162
+
163
+ test "shows text field using formatted date and time with format" do
164
+ @product.published_at = Time.local(2009, 3, 1, 12, 0, 0)
165
+ # careful - leading whitespace with %e
166
+ assert_dom_equal '<input id="product_published_at" name="product[published_at]" size="30" type="text" value=" 1. März, 12:00 Uhr" />',
167
+ text_field(:product, :published_at, :format => :short)
168
+ end
169
+
170
+ test "shows text field using formatted time with format" do
171
+ @product.cant_think_of_a_sensible_time_field = Time.local(2009, 3, 1, 9, 0, 0)
172
+ assert_dom_equal '<input id="product_cant_think_of_a_sensible_time_field" name="product[cant_think_of_a_sensible_time_field]" size="30" type="text" value="09:00 Uhr" />',
173
+ text_field(:product, :cant_think_of_a_sensible_time_field, :format => :time)
174
+ end
175
+
176
+ test "doesn't raise an exception when object is nil" do
177
+ assert_nothing_raised {
178
+ text_field(:not_here, :a_text_field)
179
+ }
180
+ end
181
+
182
+ test "doesn't raise for nil Date/Time" do
183
+ @product.published_at, @product.released_on, @product.cant_think_of_a_sensible_time_field = nil
184
+ assert_nothing_raised {
185
+ text_field(:product, :published_at)
186
+ text_field(:product, :released_on)
187
+ text_field(:product, :cant_think_of_a_sensible_time_field)
188
+ }
189
+ end
190
+
191
+ test "doesn't override given :value" do
192
+ @product.price = 1299.9
193
+ assert_dom_equal '<input id="product_price" name="product[price]" size="30" type="text" value="1.499,90" />',
194
+ text_field(:product, :price, :value => "1.499,90")
195
+ end
196
+
197
+ test "doesn't raise an exception when object isn't an ActiveReccord" do
198
+ @product = NonArProduct.new
199
+ assert_nothing_raised {
200
+ text_field(:product, :name)
201
+ text_field(:product, :times_sold)
202
+ text_field(:product, :published_at)
203
+ text_field(:product, :released_on)
204
+ text_field(:product, :cant_think_of_a_sensible_time_field)
205
+ text_field(:product, :price, :value => "1.499,90")
206
+ }
207
+ end
208
+ end
@@ -0,0 +1,71 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path(File.dirname(__FILE__) + "/../../../../config/environment")
3
+ require 'test_help'
4
+
5
+ require 'rubygems'
6
+ require 'active_record'
7
+ require 'active_record/test_case'
8
+ require 'action_view'
9
+ require 'action_view/test_case'
10
+
11
+ I18n.backend.store_translations :de, {
12
+ :date => {
13
+ :input => {
14
+ :formats => [:long, :short, :default]
15
+ },
16
+ :formats => {
17
+ :default => "%d.%m.%Y",
18
+ :short => "%e. %b",
19
+ :long => "%e. %B %Y",
20
+ :only_day => "%e"
21
+ },
22
+ :day_names => %w(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag),
23
+ :abbr_day_names => %w(So Mo Di Mi Do Fr Sa),
24
+ :month_names => [nil] + %w(Januar Februar März April Mai Juni Juli August September Oktober November Dezember),
25
+ :abbr_month_names => [nil] + %w(Jan Feb Mär Apr Mai Jun Jul Aug Sep Okt Nov Dez)
26
+ },
27
+ :time => {
28
+ :input => {
29
+ :formats => [:long, :medium, :short, :default, :time]
30
+ },
31
+ :formats => {
32
+ :default => "%A, %e. %B %Y, %H:%M Uhr",
33
+ :short => "%e. %B, %H:%M Uhr",
34
+ :medium => "%e. %B %Y, %H:%M Uhr",
35
+ :long => "%A, %e. %B %Y, %H:%M Uhr",
36
+ :time => "%H:%M Uhr"
37
+ },
38
+ :am => 'vormittags',
39
+ :pm => 'nachmittags'
40
+ },
41
+ :number => {
42
+ :format => {
43
+ :precision => 2,
44
+ :separator => ',',
45
+ :delimiter => '.'
46
+ }
47
+ }
48
+ }
49
+
50
+ I18n.locale = :de
51
+
52
+ class NonArProduct
53
+ attr_accessor :name, :price, :times_sold,
54
+ :cant_think_of_a_sensible_time_field,
55
+ :released_on, :published_at
56
+ end
57
+
58
+ class Product < ActiveRecord::Base
59
+ end
60
+
61
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
62
+ ActiveRecord::Base.establish_connection(config['test'])
63
+
64
+ ActiveRecord::Base.connection.create_table :products do |t|
65
+ t.string :name
66
+ t.date :released_on
67
+ t.datetime :published_at
68
+ t.time :cant_think_of_a_sensible_time_field
69
+ t.decimal :price
70
+ t.integer :times_sold
71
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: delocalize
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Clemens Kofler
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-31 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Delocalize is a tool for parsing localized dates/times and numbers.
17
+ email: clemens@railway.at
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - MIT-LICENSE
26
+ - README
27
+ - Rakefile
28
+ - VERSION
29
+ - init.rb
30
+ - lib/delocalize.rb
31
+ - lib/delocalize/i18n_ext.rb
32
+ - lib/delocalize/localized_date_time_parser.rb
33
+ - lib/delocalize/rails_ext.rb
34
+ - lib/delocalize/rails_ext/action_view.rb
35
+ - lib/delocalize/rails_ext/active_record.rb
36
+ - lib/delocalize/rails_ext/time_zone.rb
37
+ - lib/delocalize/ruby_ext.rb
38
+ - lib/delocalize/ruby_ext/date.rb
39
+ - lib/delocalize/ruby_ext/datetime.rb
40
+ - lib/delocalize/ruby_ext/time.rb
41
+ - tasks/distribution.rb
42
+ - tasks/documentation.rb
43
+ - tasks/testing.rb
44
+ has_rdoc: true
45
+ homepage: http://github.com/clemens/delocalize
46
+ licenses: []
47
+
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --charset=UTF-8
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.3.5
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Localized date/time and number parsing
72
+ test_files:
73
+ - test/delocalize_test.rb
74
+ - test/test_helper.rb