From 11cc4dcfc63adaea5901f6d499b84612e5b3ad0e Mon Sep 17 00:00:00 2001 From: "cira@chromium.org" Date: Thu, 19 May 2011 21:12:47 +0000 Subject: [PATCH] Adding DateTimeFormat class to i18n API with following methods: - format - getWeekdays - getMonths - get Eras - getAmPm TEST=Visit i18n.kaziprst.org/datetimeformat.html Review URL: http://codereview.chromium.org/7014019 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@7967 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/extensions/experimental/datetime-format.cc | 381 +++++++++++++++++++++++++ src/extensions/experimental/datetime-format.h | 83 ++++++ src/extensions/experimental/experimental.gyp | 2 + src/extensions/experimental/i18n-extension.cc | 3 + src/extensions/experimental/i18n-utils.cc | 25 ++ src/extensions/experimental/i18n-utils.h | 14 + src/extensions/experimental/i18n.js | 109 ++++++- 7 files changed, 606 insertions(+), 11 deletions(-) create mode 100644 src/extensions/experimental/datetime-format.cc create mode 100644 src/extensions/experimental/datetime-format.h diff --git a/src/extensions/experimental/datetime-format.cc b/src/extensions/experimental/datetime-format.cc new file mode 100644 index 0000000..ad8986a --- /dev/null +++ b/src/extensions/experimental/datetime-format.cc @@ -0,0 +1,381 @@ +// Copyright 2011 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "datetime-format.h" + +#include "i18n-utils.h" +#include "unicode/dtfmtsym.h" +#include "unicode/dtptngen.h" +#include "unicode/locid.h" +#include "unicode/smpdtfmt.h" + +namespace v8 { +namespace internal { + +v8::Persistent DateTimeFormat::datetime_format_template_; + +static icu::DateFormat* CreateDateTimeFormat(v8::Handle, + v8::Handle); +static v8::Handle GetSymbols( + const v8::Arguments&, + const icu::UnicodeString*, int32_t, + const icu::UnicodeString*, int32_t, + const icu::UnicodeString*, int32_t); +static v8::Handle ThrowUnexpectedObjectError(); +static icu::DateFormat::EStyle GetDateTimeStyle(const icu::UnicodeString&); + +icu::SimpleDateFormat* DateTimeFormat::UnpackDateTimeFormat( + v8::Handle obj) { + if (datetime_format_template_->HasInstance(obj)) { + return static_cast( + obj->GetPointerFromInternalField(0)); + } + + return NULL; +} + +void DateTimeFormat::DeleteDateTimeFormat(v8::Persistent object, + void* param) { + v8::Persistent persistent_object = + v8::Persistent::Cast(object); + + // First delete the hidden C++ object. + // Unpacking should never return NULL here. That would only happen if + // this method is used as the weak callback for persistent handles not + // pointing to a date time formatter. + delete UnpackDateTimeFormat(persistent_object); + + // Then dispose of the persistent handle to JS object. + persistent_object.Dispose(); +} + +v8::Handle DateTimeFormat::Format(const v8::Arguments& args) { + v8::HandleScope handle_scope; + + double millis = 0.0; + if (args.Length() != 1 || !args[0]->IsDate()) { + // Create a new date. + v8::TryCatch try_catch; + v8::Local date_script = + v8::Script::Compile(v8::String::New("eval('new Date()')")); + millis = date_script->Run()->NumberValue(); + if (try_catch.HasCaught()) { + return try_catch.ReThrow(); + } + } else { + millis = v8::Date::Cast(*args[0])->NumberValue(); + } + + icu::SimpleDateFormat* date_format = UnpackDateTimeFormat(args.Holder()); + if (!date_format) { + return ThrowUnexpectedObjectError(); + } + + icu::UnicodeString result; + date_format->format(millis, result); + + return v8::String::New( + reinterpret_cast(result.getBuffer()), result.length()); +} + +v8::Handle DateTimeFormat::GetMonths(const v8::Arguments& args) { + icu::SimpleDateFormat* date_format = UnpackDateTimeFormat(args.Holder()); + if (!date_format) { + return ThrowUnexpectedObjectError(); + } + + const icu::DateFormatSymbols* symbols = date_format->getDateFormatSymbols(); + + int32_t narrow_count; + const icu::UnicodeString* narrow = symbols->getMonths( + narrow_count, + icu::DateFormatSymbols::STANDALONE, + icu::DateFormatSymbols::NARROW); + int32_t abbrev_count; + const icu::UnicodeString* abbrev = symbols->getMonths( + abbrev_count, + icu::DateFormatSymbols::STANDALONE, + icu::DateFormatSymbols::ABBREVIATED); + int32_t wide_count; + const icu::UnicodeString* wide = symbols->getMonths( + wide_count, + icu::DateFormatSymbols::STANDALONE, + icu::DateFormatSymbols::WIDE); + + return GetSymbols( + args, narrow, narrow_count, abbrev, abbrev_count, wide, wide_count); +} + +v8::Handle DateTimeFormat::GetWeekdays(const v8::Arguments& args) { + icu::SimpleDateFormat* date_format = UnpackDateTimeFormat(args.Holder()); + if (!date_format) { + ThrowUnexpectedObjectError(); + } + + const icu::DateFormatSymbols* symbols = date_format->getDateFormatSymbols(); + + int32_t narrow_count; + const icu::UnicodeString* narrow = symbols->getWeekdays( + narrow_count, + icu::DateFormatSymbols::STANDALONE, + icu::DateFormatSymbols::NARROW); + int32_t abbrev_count; + const icu::UnicodeString* abbrev = symbols->getWeekdays( + abbrev_count, + icu::DateFormatSymbols::STANDALONE, + icu::DateFormatSymbols::ABBREVIATED); + int32_t wide_count; + const icu::UnicodeString* wide = symbols->getWeekdays( + wide_count, + icu::DateFormatSymbols::STANDALONE, + icu::DateFormatSymbols::WIDE); + + // getXXXWeekdays always returns 8 elements. + ASSERT_EQ(8, narrow_count); + ASSERT_EQ(8, abbrev_count); + ASSERT_EQ(8, wide_count); + + // ICU documentation says we should ignore element 0 of the returned array. + return GetSymbols(args, narrow + 1, narrow_count - 1, abbrev + 1, + abbrev_count -1 , wide + 1, wide_count - 1); +} + +v8::Handle DateTimeFormat::GetEras(const v8::Arguments& args) { + icu::SimpleDateFormat* date_format = UnpackDateTimeFormat(args.Holder()); + if (!date_format) { + return ThrowUnexpectedObjectError(); + } + + const icu::DateFormatSymbols* symbols = date_format->getDateFormatSymbols(); + + int32_t narrow_count; + const icu::UnicodeString* narrow = symbols->getNarrowEras(narrow_count); + int32_t abbrev_count; + const icu::UnicodeString* abbrev = symbols->getEras(abbrev_count); + int32_t wide_count; + const icu::UnicodeString* wide = symbols->getEraNames(wide_count); + + return GetSymbols( + args, narrow, narrow_count, abbrev, abbrev_count, wide, wide_count); +} + +v8::Handle DateTimeFormat::GetAmPm(const v8::Arguments& args) { + icu::SimpleDateFormat* date_format = UnpackDateTimeFormat(args.Holder()); + if (!date_format) { + return ThrowUnexpectedObjectError(); + } + + const icu::DateFormatSymbols* symbols = date_format->getDateFormatSymbols(); + + // In this case narrow == abbreviated == wide + int32_t count; + const icu::UnicodeString* wide = symbols->getAmPmStrings(count); + + return GetSymbols(args, wide, count, wide, count, wide, count); +} + +v8::Handle DateTimeFormat::JSDateTimeFormat( + const v8::Arguments& args) { + v8::HandleScope handle_scope; + + ASSERT_EQ(2, args.Length()); + // LanguageID string. + ASSERT(args[0]->IsString()); + // Settings object. + ASSERT(args[1]->IsObject()); + + icu::SimpleDateFormat* date_format = static_cast( + CreateDateTimeFormat(args[0]->ToString(), args[1]->ToObject())); + + if (datetime_format_template_.IsEmpty()) { + v8::Local raw_template(v8::FunctionTemplate::New()); + + raw_template->SetClassName(v8::String::New("v8Locale.DateTimeFormat")); + + // Define internal field count on instance template. + v8::Local object_template = + raw_template->InstanceTemplate(); + + // Set aside internal field for icu date time formatter. + object_template->SetInternalFieldCount(1); + + // Define all of the prototype methods on prototype template. + v8::Local proto = raw_template->PrototypeTemplate(); + proto->Set(v8::String::New("format"), + v8::FunctionTemplate::New(Format)); + proto->Set(v8::String::New("getMonths"), + v8::FunctionTemplate::New(GetMonths)); + proto->Set(v8::String::New("getWeekdays"), + v8::FunctionTemplate::New(GetWeekdays)); + proto->Set(v8::String::New("getEras"), + v8::FunctionTemplate::New(GetEras)); + proto->Set(v8::String::New("getAmPm"), + v8::FunctionTemplate::New(GetAmPm)); + + datetime_format_template_ = + v8::Persistent::New(raw_template); + } + + // Create an empty object wrapper. + v8::Local local_object = + datetime_format_template_->GetFunction()->NewInstance(); + v8::Persistent wrapper = + v8::Persistent::New(local_object); + + // Set date time formatter as internal field of the resulting JS object. + wrapper->SetPointerInInternalField(0, date_format); + + // Set resolved pattern in options.pattern. + icu::UnicodeString pattern; + date_format->toPattern(pattern); + v8::Local options = v8::Object::New(); + options->Set(v8::String::New("pattern"), + v8::String::New(reinterpret_cast( + pattern.getBuffer()), pattern.length())); + wrapper->Set(v8::String::New("options"), options); + + // Make object handle weak so we can delete iterator once GC kicks in. + wrapper.MakeWeak(NULL, DeleteDateTimeFormat); + + return wrapper; +} + +// Returns SimpleDateFormat. +static icu::DateFormat* CreateDateTimeFormat( + v8::Handle locale, v8::Handle settings) { + v8::HandleScope handle_scope; + + v8::String::AsciiValue ascii_locale(locale); + icu::Locale icu_locale(*ascii_locale); + + // Make formatter from skeleton. + icu::SimpleDateFormat* date_format = NULL; + UErrorCode status = U_ZERO_ERROR; + icu::UnicodeString skeleton; + if (I18NUtils::ExtractStringSetting(settings, "skeleton", &skeleton)) { + v8::Local generator( + icu::DateTimePatternGenerator::createInstance(icu_locale, status)); + icu::UnicodeString pattern = + generator->getBestPattern(skeleton, status); + + date_format = new icu::SimpleDateFormat(pattern, icu_locale, status); + if (U_SUCCESS(status)) { + return date_format; + } else { + delete date_format; + } + } + + // Extract date type and time type from settings. + icu::UnicodeString date_type; + icu::DateFormat::EStyle date_style = icu::DateFormat::kNone; + if (I18NUtils::ExtractStringSetting(settings, "dateType", &date_type)) { + date_style = GetDateTimeStyle(date_type); + } + + icu::UnicodeString time_type; + icu::DateFormat::EStyle time_style = icu::DateFormat::kNone; + if (I18NUtils::ExtractStringSetting(settings, "timeType", &time_type)) { + time_style = GetDateTimeStyle(time_type); + } + + // Try all combinations of date/time types. + if (date_style == icu::DateFormat::kNone && + time_style == icu::DateFormat::kNone) { + // Return default short date, short + return icu::DateFormat::createDateTimeInstance( + icu::DateFormat::kShort, icu::DateFormat::kShort, icu_locale); + } else if (date_style != icu::DateFormat::kNone && + time_style != icu::DateFormat::kNone) { + return icu::DateFormat::createDateTimeInstance( + date_style, time_style, icu_locale); + } else if (date_style != icu::DateFormat::kNone) { + return icu::DateFormat::createDateInstance(date_style, icu_locale); + } else { + // time_style != icu::DateFormat::kNone + return icu::DateFormat::createTimeInstance(time_style, icu_locale); + } +} + +// Creates a v8::Array of narrow, abbrev or wide symbols. +static v8::Handle GetSymbols(const v8::Arguments& args, + const icu::UnicodeString* narrow, + int32_t narrow_count, + const icu::UnicodeString* abbrev, + int32_t abbrev_count, + const icu::UnicodeString* wide, + int32_t wide_count) { + v8::HandleScope handle_scope; + + // Make wide width default. + const icu::UnicodeString* result = wide; + int32_t count = wide_count; + + if (args.Length() == 1 && args[0]->IsString()) { + v8::String::AsciiValue ascii_value(args[0]); + if (strcmp(*ascii_value, "abbreviated") == 0) { + result = abbrev; + count = abbrev_count; + } else if (strcmp(*ascii_value, "narrow") == 0) { + result = narrow; + count = narrow_count; + } + } + + v8::Handle symbols = v8::Array::New(); + for (int32_t i = 0; i < count; ++i) { + symbols->Set(i, v8::String::New( + reinterpret_cast(result[i].getBuffer()), + result[i].length())); + } + + return handle_scope.Close(symbols); +} + +// Throws a JavaScript exception. +static v8::Handle ThrowUnexpectedObjectError() { + // Returns undefined, and schedules an exception to be thrown. + return v8::ThrowException(v8::Exception::Error( + v8::String::New("DateTimeFormat method called on an object " + "that is not a DateTimeFormat."))); +} + +// Returns icu date/time style. +static icu::DateFormat::EStyle GetDateTimeStyle( + const icu::UnicodeString& type) { + if (type == UNICODE_STRING_SIMPLE("medium")) { + return icu::DateFormat::kMedium; + } else if (type == UNICODE_STRING_SIMPLE("long")) { + return icu::DateFormat::kLong; + } else if (type == UNICODE_STRING_SIMPLE("full")) { + return icu::DateFormat::kFull; + } + + return icu::DateFormat::kShort; +} + +} } // namespace v8::internal diff --git a/src/extensions/experimental/datetime-format.h b/src/extensions/experimental/datetime-format.h new file mode 100644 index 0000000..5b5abf4 --- /dev/null +++ b/src/extensions/experimental/datetime-format.h @@ -0,0 +1,83 @@ +// Copyright 2011 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef V8_EXTENSIONS_EXPERIMENTAL_DATETIME_FORMAT_H_ +#define V8_EXTENSIONS_EXPERIMENTAL_DATETIME_FORMAT_H_ + +#include + +#include "unicode/uversion.h" + +namespace U_ICU_NAMESPACE { +class SimpleDateFormat; +} + +namespace v8 { +namespace internal { + +class DateTimeFormat { + public: + static v8::Handle JSDateTimeFormat(const v8::Arguments& args); + + // Helper methods for various bindings. + + // Unpacks date format object from corresponding JavaScript object. + static icu::SimpleDateFormat* UnpackDateTimeFormat( + v8::Handle obj); + + // Release memory we allocated for the DateFormat once the JS object that + // holds the pointer gets garbage collected. + static void DeleteDateTimeFormat(v8::Persistent object, + void* param); + + // Formats date and returns corresponding string. + static v8::Handle Format(const v8::Arguments& args); + + // All date time symbol methods below return stand-alone names in + // either narrow, abbreviated or wide width. + + // Get list of months. + static v8::Handle GetMonths(const v8::Arguments& args); + + // Get list of weekdays. + static v8::Handle GetWeekdays(const v8::Arguments& args); + + // Get list of eras. + static v8::Handle GetEras(const v8::Arguments& args); + + // Get list of day periods. + static v8::Handle GetAmPm(const v8::Arguments& args); + + private: + DateTimeFormat(); + + static v8::Persistent datetime_format_template_; +}; + +} } // namespace v8::internal + +#endif // V8_EXTENSIONS_EXPERIMENTAL_DATETIME_FORMAT_H_ diff --git a/src/extensions/experimental/experimental.gyp b/src/extensions/experimental/experimental.gyp index 2a7775e..79d55ba 100644 --- a/src/extensions/experimental/experimental.gyp +++ b/src/extensions/experimental/experimental.gyp @@ -41,6 +41,8 @@ 'break-iterator.h', 'collator.cc', 'collator.h', + 'datetime-format.cc', + 'datetime-format.h', 'i18n-extension.cc', 'i18n-extension.h', 'i18n-locale.cc', diff --git a/src/extensions/experimental/i18n-extension.cc b/src/extensions/experimental/i18n-extension.cc index 88c609e..79affaf 100644 --- a/src/extensions/experimental/i18n-extension.cc +++ b/src/extensions/experimental/i18n-extension.cc @@ -29,6 +29,7 @@ #include "break-iterator.h" #include "collator.h" +#include "datetime-format.h" #include "i18n-locale.h" #include "natives.h" @@ -59,6 +60,8 @@ v8::Handle I18NExtension::GetNativeFunction( return v8::FunctionTemplate::New(BreakIterator::JSBreakIterator); } else if (name->Equals(v8::String::New("NativeJSCollator"))) { return v8::FunctionTemplate::New(Collator::JSCollator); + } else if (name->Equals(v8::String::New("NativeJSDateTimeFormat"))) { + return v8::FunctionTemplate::New(DateTimeFormat::JSDateTimeFormat); } return v8::Handle(); diff --git a/src/extensions/experimental/i18n-utils.cc b/src/extensions/experimental/i18n-utils.cc index a82c8eb..f00e363 100644 --- a/src/extensions/experimental/i18n-utils.cc +++ b/src/extensions/experimental/i18n-utils.cc @@ -29,6 +29,8 @@ #include +#include "unicode/unistr.h" + namespace v8 { namespace internal { @@ -40,4 +42,27 @@ void I18NUtils::StrNCopy(char* dest, int length, const char* src) { dest[length - 1] = '\0'; } +// static +bool I18NUtils::ExtractStringSetting(const v8::Handle& settings, + const char* setting, + icu::UnicodeString* result) { + if (!setting || !result) return false; + + v8::HandleScope handle_scope; + v8::TryCatch try_catch; + v8::Handle value = settings->Get(v8::String::New(setting)); + if (try_catch.HasCaught()) { + return false; + } + // No need to check if |value| is empty because it's taken care of + // by TryCatch above. + if (!value->IsUndefined() && !value->IsNull() && value->IsString()) { + v8::String::Utf8Value utf8_value(value); + if (*utf8_value == NULL) return false; + result->setTo(icu::UnicodeString::fromUTF8(*utf8_value)); + return true; + } + return false; +} + } } // namespace v8::internal diff --git a/src/extensions/experimental/i18n-utils.h b/src/extensions/experimental/i18n-utils.h index 7702708..b5e215e 100644 --- a/src/extensions/experimental/i18n-utils.h +++ b/src/extensions/experimental/i18n-utils.h @@ -28,6 +28,14 @@ #ifndef V8_EXTENSIONS_EXPERIMENTAL_I18N_UTILS_H_ #define V8_EXTENSIONS_EXPERIMENTAL_I18N_UTILS_H_ +#include + +#include "unicode/uversion.h" + +namespace U_ICU_NAMESPACE { +class UnicodeString; +} + namespace v8 { namespace internal { @@ -40,6 +48,12 @@ class I18NUtils { // TODO(cira): Find a way to use OS::SNPrintF instead. static void StrNCopy(char* dest, int length, const char* src); + // Extract a string setting named in |settings| and set it to |result|. + // Return true if it's specified. Otherwise, return false. + static bool ExtractStringSetting(const v8::Handle& settings, + const char* setting, + icu::UnicodeString* result); + private: I18NUtils() {} }; diff --git a/src/extensions/experimental/i18n.js b/src/extensions/experimental/i18n.js index 0fa7ae7..022e763 100644 --- a/src/extensions/experimental/i18n.js +++ b/src/extensions/experimental/i18n.js @@ -45,11 +45,11 @@ v8Locale = function(settings) { } var properties = NativeJSLocale( - v8Locale.createSettingsOrDefault_(settings, {'localeID': 'root'})); + v8Locale.__createSettingsOrDefault(settings, {'localeID': 'root'})); // Keep the resolved ICU locale ID around to avoid resolving localeID to // ICU locale ID every time BreakIterator, Collator and so forth are called. - this.__icuLocaleID__ = properties.icuLocaleID; + this.__icuLocaleID = properties.icuLocaleID; this.options = {'localeID': properties.localeID, 'regionID': properties.regionID}; }; @@ -61,7 +61,7 @@ v8Locale = function(settings) { */ v8Locale.prototype.derive = function(settings) { return new v8Locale( - v8Locale.createSettingsOrDefault_(settings, this.options)); + v8Locale.__createSettingsOrDefault(settings, this.options)); }; /** @@ -79,9 +79,9 @@ v8Locale.prototype.derive = function(settings) { v8Locale.v8BreakIterator = function(locale, type) { native function NativeJSBreakIterator(); - locale = v8Locale.createLocaleOrDefault_(locale); + locale = v8Locale.__createLocaleOrDefault(locale); // BCP47 ID would work in this case, but we use ICU locale for consistency. - var iterator = NativeJSBreakIterator(locale.__icuLocaleID__, type); + var iterator = NativeJSBreakIterator(locale.__icuLocaleID, type); iterator.type = type; return iterator; }; @@ -122,22 +122,103 @@ v8Locale.prototype.v8CreateBreakIterator = function(type) { v8Locale.Collator = function(locale, settings) { native function NativeJSCollator(); - locale = v8Locale.createLocaleOrDefault_(locale); + locale = v8Locale.__createLocaleOrDefault(locale); var collator = NativeJSCollator( - locale.__icuLocaleID__, v8Locale.createSettingsOrDefault_(settings, {})); + locale.__icuLocaleID, v8Locale.__createSettingsOrDefault(settings, {})); return collator; }; /** * Creates new Collator based on current locale. * @param {Object} - collation flags. See constructor. - * @returns {Object} - new v8BreakIterator object. + * @returns {Object} - new Collator object. */ v8Locale.prototype.createCollator = function(settings) { return new v8Locale.Collator(this, settings); }; /** + * DateTimeFormat class implements locale-aware date and time formatting. + * Constructor is not part of public API. + * @param {Object} locale - locale object to pass to formatter. + * @param {Object} settings - formatting flags: + * - skeleton + * - dateType + * - timeType + * - calendar + * @constructor + */ +v8Locale.__DateTimeFormat = function(locale, settings) { + native function NativeJSDateTimeFormat(); + + settings = v8Locale.__createSettingsOrDefault(settings, {}); + + var cleanSettings = {}; + if (settings.hasOwnProperty('skeleton')) { + cleanSettings['skeleton'] = settings['skeleton']; + } else { + cleanSettings = {}; + if (settings.hasOwnProperty('dateType')) { + var dt = settings['dateType']; + if (!/^short|medium|long|full$/.test(dt)) dt = 'short'; + cleanSettings['dateType'] = dt; + } + + if (settings.hasOwnProperty('timeType')) { + var tt = settings['timeType']; + if (!/^short|medium|long|full$/.test(tt)) tt = 'short'; + cleanSettings['timeType'] = tt; + } + } + + // Default is to show short date and time. + if (!cleanSettings.hasOwnProperty('skeleton') && + !cleanSettings.hasOwnProperty('dateType') && + !cleanSettings.hasOwnProperty('timeType')) { + cleanSettings = {'dateType': 'short', + 'timeType': 'short'}; + } + + locale = v8Locale.__createLocaleOrDefault(locale); + var formatter = NativeJSDateTimeFormat(locale.__icuLocaleID, cleanSettings); + + // NativeJSDateTimeFormat creates formatter.options for us, we just need + // to append actual settings to it. + for (key in cleanSettings) { + formatter.options[key] = cleanSettings[key]; + } + + /** + * Clones existing date time format with possible overrides for some + * of the options. + * @param {!Object} overrideSettings - overrides for current format settings. + * @returns {Object} - new DateTimeFormat object. + */ + formatter.derive = function(overrideSettings) { + // To remove a setting user can specify undefined as its value. We'll remove + // it from the map in that case. + for (var prop in overrideSettings) { + if (settings.hasOwnProperty(prop) && !overrideSettings[prop]) { + delete settings[prop]; + } + } + return new v8Locale.__DateTimeFormat( + locale, v8Locale.__createSettingsOrDefault(overrideSettings, settings)); + }; + + return formatter; +}; + +/** + * Creates new DateTimeFormat based on current locale. + * @param {Object} - formatting flags. See constructor. + * @returns {Object} - new DateTimeFormat object. + */ +v8Locale.prototype.createDateTimeFormat = function(settings) { + return new v8Locale.__DateTimeFormat(this, settings); +}; + +/** * Merges user settings and defaults. * Settings that are not of object type are rejected. * Actual property values are not validated, but whitespace is trimmed if they @@ -146,7 +227,7 @@ v8Locale.prototype.createCollator = function(settings) { * @param {!Object} defaults - default values for this type of settings. * @returns {Object} - valid settings object. */ -v8Locale.createSettingsOrDefault_ = function(settings, defaults) { +v8Locale.__createSettingsOrDefault = function(settings, defaults) { if (!settings || typeof(settings) !== 'object' ) { return defaults; } @@ -155,11 +236,17 @@ v8Locale.createSettingsOrDefault_ = function(settings, defaults) { settings[key] = defaults[key]; } } - // Clean up values, like trimming whitespace. + // Clean up settings. for (var key in settings) { + // Trim whitespace. if (typeof(settings[key]) === "string") { settings[key] = settings[key].trim(); } + // Remove all properties that are set to undefined/null. This allows + // derive method to remove a setting we don't need anymore. + if (!settings[key]) { + delete settings[key]; + } } return settings; @@ -171,7 +258,7 @@ v8Locale.createSettingsOrDefault_ = function(settings, defaults) { * @param {!Object} locale - user provided locale. * @returns {Object} - v8Locale object. */ -v8Locale.createLocaleOrDefault_ = function(locale) { +v8Locale.__createLocaleOrDefault = function(locale) { if (!locale || !(locale instanceof v8Locale)) { return new v8Locale(); } else { -- 2.7.4