From b82605a44d1bf4d5caace74347e7d31f57f11e59 Mon Sep 17 00:00:00 2001 From: Cameron Dutro Date: Tue, 11 Dec 2012 15:41:30 -0800 Subject: [PATCH] Providing better access to calendar data, including eras, periods, quarters, and fields. --- lib/twitter_cldr/shared/calendar.rb | 105 +++++++++++++++++++++--- spec/shared/calendar_spec.rb | 122 ++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+), 11 deletions(-) diff --git a/lib/twitter_cldr/shared/calendar.rb b/lib/twitter_cldr/shared/calendar.rb index e6d85fec2..eab2b48ef 100644 --- a/lib/twitter_cldr/shared/calendar.rb +++ b/lib/twitter_cldr/shared/calendar.rb @@ -8,8 +8,27 @@ module Shared class Calendar DEFAULT_FORMAT = :'stand-alone' + DEFAULT_PERIOD_FORMAT = :format NAMES_FORMS = [:wide, :narrow, :abbreviated] + ERAS_NAMES_FORMS = [:abbr, :name] + + DATETIME_METHOD_MAP = { + :year_of_week_of_year => :year, + :quarter_stand_alone => :quarter, + :month_stand_alone => :month, + :day_of_month => :day, + :day_of_week_in_month => :day, + :weekday_local => :weekday, + :weekday_local_stand_alone => :weekday, + :second_fraction => :second, + :timezone_generic_non_location => :timezone, + :timezone_metazone => :timezone + } + + REDIRECT_CONVERSIONS = { + :dayPeriods => :periods + } attr_reader :locale, :calendar_type @@ -18,29 +37,93 @@ def initialize(locale = TwitterCldr.get_locale, calendar_type = TwitterCldr::DEF @calendar_type = calendar_type end - def months(names_form = :wide) - data = get_with_names_form(:months, names_form) - data && data.sort_by{ |m| m.first }.map { |m| m.last } + def months(names_form = :wide, format = DEFAULT_FORMAT) + data = get_with_names_form(:months, names_form, format) + data && data.sort_by { |m| m.first }.map { |m| m.last } end - def weekdays(names_form = :wide) - get_with_names_form(:days, names_form) + def weekdays(names_form = :wide, format = DEFAULT_FORMAT) + get_with_names_form(:days, names_form, format) + end + + def fields + get_data(:fields) + end + + def quarters(names_form = :wide, format = DEFAULT_FORMAT) + get_with_names_form(:quarters, names_form, format) + end + + def periods(names_form = :wide, format = DEFAULT_PERIOD_FORMAT) + get_with_names_form(:periods, names_form, format) + end + + def eras(names_form = :name) + get_data(:eras)[names_form] + end + + def date_order(options = {}) + get_order_for(TwitterCldr::Tokenizers::DateTokenizer, options) + end + + def time_order(options = {}) + get_order_for(TwitterCldr::Tokenizers::TimeTokenizer, options) + end + + def datetime_order(options = {}) + get_order_for(TwitterCldr::Tokenizers::DateTimeTokenizer, options) end private - def get_with_names_form(data_type, names_form) - get_data(data_type, DEFAULT_FORMAT, names_form) if NAMES_FORMS.include?(names_form.to_sym) + def calendar_cache + @@calendar_cache ||= {} + end + + def get_order_for(const, options) + opts = options.merge(:locale => @locale) + cache_key = TwitterCldr::Utils.compute_cache_key([const.to_s] + opts.keys.sort + opts.values.sort) + calendar_cache.fetch(cache_key) do |key| + tokens = const.new(opts).tokens + calendar_cache[cache_key] = resolve_methods(methods_for_tokens(tokens)) + end + end + + def resolve_methods(methods) + methods.map { |method| DATETIME_METHOD_MAP.fetch(method, method) } + end + + def methods_for_tokens(tokens) + tokens.inject([]) do |ret, token| + if token.type == :pattern + ret << TwitterCldr::Formatters::DateTimeFormatter::METHODS[token.value[0].chr] + end + ret + end + end + + def get_with_names_form(data_type, names_form, format) + get_data(data_type, format, names_form) if NAMES_FORMS.include?(names_form.to_sym) end def get_data(*path) - data = TwitterCldr::Utils.traverse_hash(calendar_data, path) - redirect = parse_redirect(data) - redirect ? get_data(*redirect) : data + cache_key = TwitterCldr::Utils.compute_cache_key([@locale] + path) + calendar_cache.fetch(cache_key) do |key| + data = TwitterCldr::Utils.traverse_hash(calendar_data, path) + redirect = parse_redirect(data) + calendar_cache[key] = if redirect + get_data(*redirect) + else + data + end + end end def parse_redirect(data) - $1.split('.').map(&:to_sym) if data.is_a?(Symbol) && data.to_s =~ redirect_regexp + if data.is_a?(Symbol) && data.to_s =~ redirect_regexp + result = $1.split('.').map(&:to_sym) + result.map { |leg| REDIRECT_CONVERSIONS.fetch(leg, leg) } + end end def redirect_regexp diff --git a/spec/shared/calendar_spec.rb b/spec/shared/calendar_spec.rb index 49ac2862e..04098822a 100644 --- a/spec/shared/calendar_spec.rb +++ b/spec/shared/calendar_spec.rb @@ -11,6 +11,11 @@ let(:calendar) { Calendar.new(:de) } + before(:each) do + # clear cache for each test + Calendar.class_variable_set(:@@calendar_cache, {}) + end + describe '#initialize' do it 'returns calendar for default locale and type' do stub(TwitterCldr).get_locale { :fr } @@ -130,4 +135,121 @@ end end + describe '#fields' do + it 'returns the list of fields for the locale (eg. weekday, month, etc)' do + fields = calendar.fields + fields[:hour].should match_normalized("Stunde") + fields[:dayperiod].should match_normalized("Tageshälfte") + fields[:weekday].should match_normalized("Wochentag") + + fields = Calendar.new(:ja).fields + fields[:hour].should match_normalized("時") + fields[:dayperiod].should match_normalized("午前/午後") + fields[:weekday].should match_normalized("曜日") + end + end + + describe '#quarters' do + it 'returns default quarters' do + calendar.quarters.should == { + 1 => "1. Quartal", + 2 => "2. Quartal", + 3 => "3. Quartal", + 4 => "4. Quartal" + } + end + + it 'returns quarters with other name forms' do + calendar.quarters(:abbreviated).should == { + 1 => "Q1", 2 => "Q2", + 3 => "Q3", 4 => "Q4" + } + + calendar.quarters(:narrow).should == { + 1 => 1, 2 => 2, + 3 => 3, 4 => 4 + } + end + end + + describe '#periods' do + it 'returns default periods' do + periods = calendar.periods + periods[:am].should == "vorm." + periods[:pm].should == "nachm." + end + + it 'returns quarters with other name forms' do + periods = calendar.periods(:abbreviated) + periods[:am].should == "vorm." + periods[:pm].should == "nachm." + end + end + + describe '#eras' do + it 'returns default eras' do + calendar.eras.should == { + 0 => "v. Chr.", + 1 => "n. Chr." + } + end + + it 'returns eras with other name forms' do + calendar.eras(:abbr).should == { + 0 => "v. Chr.", + 1 => "n. Chr." + } + end + end + + describe '#date_order' do + it 'should return the correct date order for a few different locales' do + Calendar.new(:en).date_order.should == [:month, :day, :year] + Calendar.new(:ja).date_order.should == [:year, :month, :day] + Calendar.new(:ar).date_order.should == [:day, :month, :year] + end + end + + describe '#time_order' do + it 'should return the correct time order for a few different locales' do + Calendar.new(:en).time_order.should == [:hour, :minute, :second, :period] + Calendar.new(:ja).time_order.should == [:hour, :minute, :second] + Calendar.new(:ar).time_order.should == [:hour, :minute, :second, :period] + end + end + + describe '#datetime_order' do + it 'should return the correct date and time order for a few different locales' do + Calendar.new(:en).datetime_order.should == [:month, :day, :year, :hour, :minute, :second, :period] + Calendar.new(:ja).datetime_order.should == [:year, :month, :day, :hour, :minute, :second] + Calendar.new(:ar).datetime_order.should == [:day, :month, :year, :hour, :minute, :second, :period] + end + end + + describe '#methods_for_tokens' do + it 'converts pattern tokens into their corresponding method names' do + tokens = [TwitterCldr::Tokenizers::Token.new(:value => "YYYY", :type => :pattern)] + calendar.send(:methods_for_tokens, tokens).should == [:year_of_week_of_year] + end + + it 'ignores plaintext tokens' do + tokens = [TwitterCldr::Tokenizers::Token.new(:value => "blarg", :type => :plaintext)] + calendar.send(:methods_for_tokens, tokens).should == [] + end + end + + describe '#resolve_methods' do + it 'converts certain method names to their basic equivalents' do + calendar.send(:resolve_methods, [:year_of_week_of_year]).should == [:year] + calendar.send(:resolve_methods, [:weekday_local]).should == [:weekday] + calendar.send(:resolve_methods, [:day_of_month, :second_fraction]).should == [:day, :second] + end + + it 'does not convert basic method names' do + calendar.send(:resolve_methods, [:year]).should == [:year] + calendar.send(:resolve_methods, [:day, :month]).should == [:day, :month] + calendar.send(:resolve_methods, [:minute, :hour, :second]).should == [:minute, :hour, :second] + end + end + end \ No newline at end of file