Skip to content
Browse files

Add Thai buddhist year support

  • Loading branch information...
1 parent 64ff9e1 commit 4906ceee1689cadfb3e8c7a7f5a7d826f0ecdc49 Tanin committed Apr 27, 2012
View
1 .gitignore
@@ -2,3 +2,4 @@ pkg/*
Gemfile.lock
*.DS_Store
coverage/
+.bundle/*
View
6 lib/ext/calendars/date.rb
@@ -4,16 +4,16 @@
# http://www.apache.org/licenses/LICENSE-2.0
class Date
- def localize(locale = TwitterCldr.get_locale)
- TwitterCldr::LocalizedDate.new(self, locale)
+ def localize(locale = TwitterCldr.get_locale, options = {})
+ TwitterCldr::LocalizedDate.new(self, locale, options)
end
end
module TwitterCldr
class LocalizedDate < LocalizedDateTime
def to_datetime(time)
time_obj = time.is_a?(LocalizedTime) ? time.base_obj : time
- LocalizedDateTime.new(DateTime.parse("#{@base_obj.strftime("%Y-%m-%d")}T#{time_obj.strftime("%H:%M:%S%z")}"), @locale)
+ LocalizedDateTime.new(DateTime.parse("#{@base_obj.strftime("%Y-%m-%d")}T#{time_obj.strftime("%H:%M:%S%z")}"), @locale, :calendar_type => @calendar_type)
end
protected
View
15 lib/ext/calendars/datetime.rb
@@ -4,13 +4,20 @@
# http://www.apache.org/licenses/LICENSE-2.0
class DateTime
- def localize(locale = TwitterCldr.get_locale)
- TwitterCldr::LocalizedDateTime.new(self, locale)
+ def localize(locale = TwitterCldr.get_locale, options = {})
+ TwitterCldr::LocalizedDateTime.new(self, locale, options)
end
end
module TwitterCldr
class LocalizedDateTime < LocalizedObject
+ attr_reader :calendar_type
+
+ def initialize(obj, locale, options={})
+ super(obj, locale, options)
+ @calendar_type = options[:calendar_type] || TwitterCldr::DEFAULT_CALENDAR_TYPE
+ end
+
def method_missing(method, *args, &block)
type = method.to_s.match(/to_(\w+)_s/)[1]
if type && !type.empty? && TwitterCldr::Tokenizers::DateTimeTokenizer::VALID_TYPES.include?(type.to_sym)
@@ -25,11 +32,11 @@ def to_s
end
def to_date
- LocalizedDate.new(Date.parse(@base_obj.strftime("%Y-%m-%dT%H:%M:%S%z")), @locale)
+ LocalizedDate.new(Date.parse(@base_obj.strftime("%Y-%m-%dT%H:%M:%S%z")), @locale, :calendar_type => @calendar_type)
end
def to_time
- LocalizedTime.new(Time.parse(@base_obj.strftime("%Y-%m-%dT%H:%M:%S%z")), @locale)
+ LocalizedTime.new(Time.parse(@base_obj.strftime("%Y-%m-%dT%H:%M:%S%z")), @locale, :calendar_type => @calendar_type)
end
protected
View
6 lib/ext/calendars/time.rb
@@ -4,16 +4,16 @@
# http://www.apache.org/licenses/LICENSE-2.0
class Time
- def localize(locale = TwitterCldr.get_locale)
- TwitterCldr::LocalizedTime.new(self, locale)
+ def localize(locale = TwitterCldr.get_locale, options = {})
+ TwitterCldr::LocalizedTime.new(self, locale, options)
end
end
module TwitterCldr
class LocalizedTime < LocalizedDateTime
def to_datetime(date)
date_obj = date.is_a?(LocalizedDate) ? date.base_obj : date
- LocalizedDateTime.new(DateTime.parse("#{date_obj.strftime("%Y-%m-%d")}T#{@base_obj.strftime("%H:%M:%S%z")}"), @locale)
+ LocalizedDateTime.new(DateTime.parse("#{date_obj.strftime("%Y-%m-%d")}T#{@base_obj.strftime("%H:%M:%S%z")}"), @locale, :calendar_type => @calendar_type)
end
protected
View
6 lib/ext/localized_object.rb
@@ -7,10 +7,12 @@ module TwitterCldr
class LocalizedObject
attr_reader :locale, :base_obj, :formatter
- def initialize(obj, locale)
+ def initialize(obj, locale, options={})
@base_obj = obj
@locale = locale
- @formatter = self.formatter_const.new(:locale => @locale) if self.formatter_const
+
+ options[:locale] ||= @locale
+ @formatter = self.formatter_const.new(options) if self.formatter_const
end
def formatter_const
View
9 lib/formatters/base.rb
@@ -9,10 +9,17 @@ class Base
attr_reader :tokenizer
def format(obj, options = {})
+ process_tokens(self.get_tokens(obj, options), obj)
+ end
+
+ protected
+ def process_tokens(tokens, obj)
final = StringIO.new
- result = self.get_tokens(obj, options).each_with_index do |token, index|
+ tokens.each_with_index do |token, index|
case token.type
+ when :composite
+ final << eval(process_tokens(token.tokens, obj))
when :pattern
final << self.result_for_token(token, index, obj)
else
View
2 lib/formatters/calendars/date_formatter.rb
@@ -7,7 +7,7 @@ module TwitterCldr
module Formatters
class DateFormatter < DateTimeFormatter
def initialize(options = {})
- @tokenizer = TwitterCldr::Tokenizers::DateTokenizer.new(:locale => extract_locale(options))
+ @tokenizer = TwitterCldr::Tokenizers::DateTokenizer.new(:locale => extract_locale(options), :calendar_type => options[:calendar_type])
end
end
end
View
2 lib/formatters/calendars/datetime_formatter.rb
@@ -41,7 +41,7 @@ class DateTimeFormatter < Base
}
def initialize(options = {})
- @tokenizer = TwitterCldr::Tokenizers::DateTimeTokenizer.new(:locale => extract_locale(options))
+ @tokenizer = TwitterCldr::Tokenizers::DateTimeTokenizer.new(:locale => extract_locale(options), :calendar_type => options[:calendar_type])
end
def result_for_token(token, index, date)
View
2 lib/formatters/calendars/time_formatter.rb
@@ -7,7 +7,7 @@ module TwitterCldr
module Formatters
class TimeFormatter < DateTimeFormatter
def initialize(options = {})
- @tokenizer = TwitterCldr::Tokenizers::TimeTokenizer.new(:locale => extract_locale(options))
+ @tokenizer = TwitterCldr::Tokenizers::TimeTokenizer.new(:locale => extract_locale(options), :calendar_type => options[:calendar_type])
end
end
end
View
10 lib/tokenizers/base.rb
@@ -26,7 +26,13 @@ def tokenize_format(text)
unless index == 0 && token == ""
self.token_type_regexes.each do |token_type|
if token =~ token_type[:regex]
- final << Token.new(:value => token, :type => token_type[:type])
+ if token_type[:type] == :composite
+ content = token.match(token_type[:content])[1]
+ final << CompositeToken.new(tokenize_format(content))
+ else
+ final << Token.new(:value => token, :type => token_type[:type])
+ end
+
break
end
end
@@ -40,7 +46,7 @@ def tokens_for(key, type)
tokens = self.expand_pattern(self.pattern_for(self.traverse(key)), type)
tokens.each do |token|
- if token.is_a?(Token)
+ if token.is_a?(Token) || token.is_a?(CompositeToken)
final << token
else
final += tokenize_format(token[:value])
View
15 lib/tokenizers/calendars/date_tokenizer.rb
@@ -8,14 +8,15 @@ module Tokenizers
class DateTokenizer < TwitterCldr::Tokenizers::DateTimeTokenizer
def initialize(options = {})
super(options)
- @token_splitter_regex = /(\s*\'[\w\s-]+\'\s*|G{1,5}|y+|Y+|Q{1,4}|q{1,5}|M{1,5}|L{1,5}|d{1,2}|F{1}|E{1,5}|e{1,5}|c{1,5})/
- @token_type_regexes = [{ :type => :pattern, :regex => /^(?:G{1,5}|y+|Y+|Q{1,4}|q{1,5}|M{1,5}|L{1,5}|d{1,2}|F{1}|E{1,5}|e{1,5}|c{1,5})/ },
+ @token_splitter_regex = /(\s*\'[\w\s-]+\'\s*|G{1,5}|y+|Y+|Q{1,4}|q{1,5}|M{1,5}|L{1,5}|d{1,2}|F{1}|E{1,5}|e{1,5}|c{1,5}|\#\{[^\}]+\})/
+ @token_type_regexes = [ { :type => :composite, :regex => /^\#\{[^\}]+\}/, :content => /^\#\{([^\}]+)\}/ },
+ { :type => :pattern, :regex => /^(?:G{1,5}|y+|Y+|Q{1,4}|q{1,5}|M{1,5}|L{1,5}|d{1,2}|F{1}|E{1,5}|e{1,5}|c{1,5})/ },
{ :type => :plaintext, :regex => // }]
- @paths = { :default => "calendars.gregorian.formats.date.default",
- :full => "calendars.gregorian.formats.date.full",
- :long => "calendars.gregorian.formats.date.long",
- :medium => "calendars.gregorian.formats.date.medium",
- :short => "calendars.gregorian.formats.date.short" }
+ @paths = { :default => "formats.date.default",
+ :full => "formats.date.full",
+ :long => "formats.date.long",
+ :medium => "formats.date.medium",
+ :short => "formats.date.short" }
end
protected
View
48 lib/tokenizers/calendars/datetime_tokenizer.rb
@@ -8,24 +8,26 @@ module Tokenizers
class DateTimeTokenizer < Base
attr_reader :placeholders, :calendar_type
- DEFAULT_CALENDAR_TYPE = :gregorian
VALID_TYPES = [:default, :full, :long, :medium, :short]
def initialize(options = {})
- super(options)
- @calendar_type = options[:calendar_type] || DEFAULT_CALENDAR_TYPE
+ @calendar_type = options[:calendar_type] || TwitterCldr::DEFAULT_CALENDAR_TYPE
@token_splitter_regex = //
@token_type_regexes = [{ :type => :plaintext, :regex => // }]
- @paths = { :default => "calendars.gregorian.formats.datetime.default",
- :full => "calendars.gregorian.formats.datetime.full",
- :long => "calendars.gregorian.formats.datetime.long",
- :medium => "calendars.gregorian.formats.datetime.medium",
- :short => "calendars.gregorian.formats.datetime.short" }
+
+ @base_path = "calendars"
+ @paths = { :default => "formats.datetime.default",
+ :full => "formats.datetime.full",
+ :long => "formats.datetime.long",
+ :medium => "formats.datetime.medium",
+ :short => "formats.datetime.short" }
+
+ super(options)
end
def tokens(options = {})
type = options[:type] || :default
- self.tokens_for(self.paths[type], type)
+ self.tokens_for(self.full_path_for(self.paths[type]), type)
end
def calendar
@@ -34,13 +36,37 @@ def calendar
protected
+ def full_path_for(path, calendar_type = @calendar_type)
+ KeyPath.join(@base_path, calendar_type.to_s, path)
+ end
+
def init_resources
@resource = TwitterCldr.get_resource(@locale, "calendars")[TwitterCldr.convert_locale(@locale)]
+
+ @resource[:calendars].each_pair do |calendar_type, options|
+ next if calendar_type == DEFAULT_CALENDAR_TYPE
+ mirror_resource(:from => @resource[:calendars][DEFAULT_CALENDAR_TYPE], :to => @resource[:calendars][calendar_type])
+ end
+ end
+
+ def mirror_resource(options)
+ from = options[:from]
+ to = options[:to]
+
+ from.each_pair do |key, value|
+ if !to[key]
+ to[key] = from[key]
+ else
+ if to[key].is_a?(Hash) and from[key].is_a?(Hash)
+ mirror_resource(:from => from[key], :to => to[key])
+ end
+ end
+ end
end
def init_placeholders
- @placeholders = [{ :name => :date, :object => TwitterCldr::Tokenizers::DateTokenizer.new(:locale => @locale) },
- { :name => :time, :object => TwitterCldr::Tokenizers::TimeTokenizer.new(:locale => @locale) }]
+ @placeholders = [{ :name => :date, :object => TwitterCldr::Tokenizers::DateTokenizer.new(:locale => @locale, :calendar_type => @calendar_type) },
+ { :name => :time, :object => TwitterCldr::Tokenizers::TimeTokenizer.new(:locale => @locale, :calendar_type => @calendar_type) }]
end
def pattern_for(resource)
View
10 lib/tokenizers/calendars/time_tokenizer.rb
@@ -11,11 +11,11 @@ def initialize(options = {})
@token_splitter_regex = /(\'[\w\s-]+\'|a{1}|h{1,2}|H{1,2}|K{1,2}|k{1,2}|m{1,2}|s{1,2}|S+|z{1,4}|Z{1,4})/
@token_type_regexes = [{ :type => :pattern, :regex => /^a{1}|h{1,2}|H{1,2}|K{1,2}|k{1,2}|m{1,2}|s{1,2}|S+|z{1,4}|Z{1,4}/ },
{ :type => :plaintext, :regex => // }]
- @paths = { :default => "calendars.gregorian.formats.time.default",
- :full => "calendars.gregorian.formats.time.full",
- :long => "calendars.gregorian.formats.time.long",
- :medium => "calendars.gregorian.formats.time.medium",
- :short => "calendars.gregorian.formats.time.short" }
+ @paths = { :default => "formats.time.default",
+ :full => "formats.time.full",
+ :long => "formats.time.long",
+ :medium => "formats.time.medium",
+ :short => "formats.time.short" }
end
protected
View
26 lib/tokenizers/composite_token.rb
@@ -0,0 +1,26 @@
+# encoding: UTF-8
+
+# Copyright 2012 Twitter, Inc
+# http://www.apache.org/licenses/LICENSE-2.0
+
+module TwitterCldr
+ module Tokenizers
+ class CompositeToken
+
+ attr_accessor :tokens
+
+ def initialize(tokens)
+ @tokens = tokens
+ end
+
+ def type
+ :composite
+ end
+
+ def to_s
+ @tokens.map { |t| t.to_s }.join('')
+ end
+
+ end
+ end
+end
View
2 lib/twitter_cldr.rb
@@ -38,6 +38,7 @@ module TwitterCldr
extend SingleForwardable
DEFAULT_LOCALE = :en
+ DEFAULT_CALENDAR_TYPE = :gregorian
RESOURCE_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "resources")
# maps twitter locales to cldr locales
@@ -105,6 +106,7 @@ def supported_locale?(locale)
require 'tokenizers/base'
require 'tokenizers/key_path'
require 'tokenizers/token'
+require 'tokenizers/composite_token'
require 'tokenizers/calendars/datetime_tokenizer'
require 'tokenizers/calendars/date_tokenizer'
require 'tokenizers/calendars/time_tokenizer'
View
22 resources/th/calendars.yml
@@ -1,5 +1,17 @@
th:
- calendars:
+ calendars:
+ buddhist:
+ formats:
+ date:
+ default: :"calendars.buddhist.formats.date.medium"
+ full:
+ pattern: "EEEEที่ d MMMM G y"
+ long:
+ pattern: "d MMMM พ.ศ. #{y + 543}"
+ medium:
+ pattern: "d MMM #{y + 543}"
+ short:
+ pattern: "d/M/#{y + 543}"
gregorian:
days:
format:
@@ -49,11 +61,11 @@ th:
full:
pattern: "EEEEที่ d MMMM G y"
long:
- pattern: "d MMMM y"
+ pattern: "d MMMM พ.ศ. y"
medium:
pattern: "d MMM y"
short:
- pattern: d/M/yyyy
+ pattern: "d/M/y"
datetime:
default: :"calendars.gregorian.formats.datetime.medium"
full:
@@ -71,9 +83,9 @@ th:
long:
pattern: "H นาฬิกา m นาที ss วินาที z"
medium:
- pattern: "H:mm:ss"
+ pattern: "H:mm:ss น."
short:
- pattern: "H:mm"
+ pattern: "H:mm น."
months:
format:
abbreviated:
View
32 spec/ext/calendars/date_spec.rb
@@ -14,12 +14,44 @@
loc_date = date.localize
loc_date.should be_a(LocalizedDate)
loc_date.locale.should == :en
+ loc_date.calendar_type.should == :gregorian
loc_date.base_obj.should == date
loc_date = Date.today.localize(:it)
loc_date.should be_a(LocalizedDate)
loc_date.locale.should == :it
end
+
+ it "should localize with the given calendar" do
+ date = Date.today
+ loc_date = date.localize(:th, :calendar_type => :buddhist)
+ loc_date.should be_a(LocalizedDate)
+ loc_date.locale.should == :th
+ loc_date.calendar_type.should == :buddhist
+ loc_date.base_obj.should == date
+ end
+
+ it "should forward calendar_type" do
+ date = Date.today
+ loc_date = date.localize(:th, :calendar_type => :buddhist)
+ loc_date.to_datetime(Time.now).calendar_type.should == :buddhist
+ end
+ end
+
+ describe "stringify" do
+ it "should stringify with a default calendar" do
+ #Date.today.localize(:th, :calendar_type => :buddhist).to_full_s # It doesn't support era
+ Date.today.localize(:th).to_long_s
+ Date.today.localize(:th).to_medium_s
+ Date.today.localize(:th).to_short_s
+ end
+
+ it "should stringify with buddhist calendar" do
+ #Date.today.localize(:th, :calendar_type => :buddhist).to_full_s # It doesn't support era
+ Date.today.localize(:th, :calendar_type => :buddhist).to_long_s
+ Date.today.localize(:th, :calendar_type => :buddhist).to_medium_s
+ Date.today.localize(:th, :calendar_type => :buddhist).to_short_s
+ end
end
end
View
33 spec/ext/calendars/datetime_spec.rb
@@ -14,12 +14,45 @@
loc_date = date.localize
loc_date.should be_a(LocalizedDateTime)
loc_date.locale.should == :en
+ loc_date.calendar_type.should == :gregorian
loc_date.base_obj.should == date
loc_date = DateTime.now.localize(:it)
loc_date.should be_a(LocalizedDateTime)
loc_date.locale.should == :it
end
+
+ it "should localize with the given calendar" do
+ date = DateTime.now
+ loc_date = date.localize(:th, :calendar_type => :buddhist)
+ loc_date.should be_a(LocalizedDateTime)
+ loc_date.locale.should == :th
+ loc_date.calendar_type.should == :buddhist
+ loc_date.base_obj.should == date
+ end
+
+ it "should forward calendar_type" do
+ date = DateTime.now
+ loc_date = date.localize(:th, :calendar_type => :buddhist)
+ loc_date.to_date.calendar_type.should == :buddhist
+ loc_date.to_time.calendar_type.should == :buddhist
+ end
+ end
+
+ describe "stringify" do
+ it "should stringify with a default calendar" do
+ #DateTime.now.localize(:th, :calendar_type => :buddhist).to_full_s # It doesn't support era
+ DateTime.now.localize(:th).to_long_s
+ DateTime.now.localize(:th).to_medium_s
+ DateTime.now.localize(:th).to_short_s
+ end
+
+ it "should stringify with buddhist calendar" do
+ #DateTime.now.localize(:th, :calendar_type => :buddhist).to_full_s # It doesn't support era
+ DateTime.now.localize(:th, :calendar_type => :buddhist).to_long_s
+ DateTime.now.localize(:th, :calendar_type => :buddhist).to_medium_s
+ DateTime.now.localize(:th, :calendar_type => :buddhist).to_short_s
+ end
end
end
View
32 spec/ext/calendars/time_spec.rb
@@ -14,12 +14,44 @@
loc_time = time.localize
loc_time.should be_a(LocalizedTime)
loc_time.locale.should == :en
+ loc_time.calendar_type.should == :gregorian
loc_time.base_obj.should == time
loc_time = Time.now.localize(:it)
loc_time.should be_a(LocalizedTime)
loc_time.locale.should == :it
end
+
+ it "should localize with the given calendar" do
+ time = Time.now
+ loc_time = time.localize(:th, :calendar_type => :buddhist)
+ loc_time.should be_a(LocalizedTime)
+ loc_time.locale.should == :th
+ loc_time.calendar_type.should == :buddhist
+ loc_time.base_obj.should == time
+ end
+
+ it "should forward calendar_type" do
+ time = Time.now
+ loc_time = time.localize(:th, :calendar_type => :buddhist)
+ loc_time.to_datetime(Date.today).calendar_type.should == :buddhist
+ end
+ end
+
+ describe "stringify" do
+ it "should stringify with a default calendar" do
+ #Time.now.localize(:th, :calendar_type => :buddhist).to_full_s # It doesn't support era
+ Time.now.localize(:th).to_long_s
+ Time.now.localize(:th).to_medium_s
+ Time.now.localize(:th).to_short_s
+ end
+
+ it "should stringify with buddhist calendar" do
+ #Time.now.localize(:th, :calendar_type => :buddhist).to_full_s # It doesn't support era
+ Time.now.localize(:th, :calendar_type => :buddhist).to_long_s
+ Time.now.localize(:th, :calendar_type => :buddhist).to_medium_s
+ Time.now.localize(:th, :calendar_type => :buddhist).to_short_s
+ end
end
end
View
11 spec/tokenizers/calendars/date_tokenizer_spec.rb
@@ -38,5 +38,16 @@
{ :value => "EEEE", :type => :pattern }]
check_token_list(got, expected)
end
+
+ it "should tokenize composites correctly" do
+ tokenizer = DateTokenizer.new(:locale => :th, :calendar_type => :buddhist)
+ got = tokenizer.tokens(:type => :long)
+ expected = [{ :value => "d", :type => :pattern },
+ { :value => " ", :type => :plaintext },
+ { :value => "MMMM", :type => :pattern },
+ { :value => " พ.ศ. ", :type => :plaintext },
+ { :to_s => "y + 543", :type => :composite }]
+ check_token_list(got, expected)
+ end
end
end
View
26 spec/tokenizers/calendars/datetime_tokenizer_spec.rb
@@ -55,4 +55,30 @@
check_token_list(got, expected)
end
end
+
+ describe "#mirror_resource" do
+ it "should add only the missing keys" do
+ from = {:a => 1,
+ :b => { :c => 2, :d => 3},
+ :e => { :f => 4}}
+ to = {:b => { :c => 100 },
+ :e => 101}
+
+ tokenizer = DateTimeTokenizer.new
+ tokenizer.send(:mirror_resource, :from => from, :to => to)
+
+ to[:a].should == 1
+ to[:b].size.should == 2
+ to[:b][:c].should == 100
+ to[:b][:d].should == 3
+ to[:e].should == 101
+
+ from[:a].should == 1
+ from[:b].size.should == 2
+ from[:b][:c].should == 2
+ from[:b][:d].should == 3
+ from[:e].size.should == 1
+ from[:e][:f].should == 4
+ end
+ end
end
View
29 spec/tokenizers/composite_token_spec.rb
@@ -0,0 +1,29 @@
+# encoding: UTF-8
+
+# Copyright 2012 Twitter, Inc
+# http://www.apache.org/licenses/LICENSE-2.0
+
+require 'spec_helper'
+
+include TwitterCldr::Tokenizers
+
+describe CompositeToken do
+ describe "#initialize" do
+ it "should set an array of tokens" do
+ token_0 = Token.new(:type => "my_type_0", :value => "my_value_0")
+ token_1 = Token.new(:type => "my_type_1", :value => "my_value_1")
+
+ composite_token = CompositeToken.new([token_0, token_1])
+ composite_token.tokens.map { |t| t.type }.should == ["my_type_0", "my_type_1"]
+ composite_token.tokens.map { |t| t.value }.should == ["my_value_0", "my_value_1"]
+ end
+
+ it "should return content" do
+ token_0 = Token.new(:type => "my_type_0", :value => "my_value_0")
+ token_1 = Token.new(:type => "my_type_1", :value => "my_value_1")
+
+ composite_token = CompositeToken.new([token_0, token_1])
+ composite_token.to_s.should == "my_value_0my_value_1"
+ end
+ end
+end

0 comments on commit 4906cee

Please sign in to comment.
Something went wrong with that request. Please try again.