Java tutorial
/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package org.apache.cordova.globalization; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.Currency; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.PluginResult; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.annotation.TargetApi; import android.text.format.Time; /** * */ public class Globalization extends CordovaPlugin { //GlobalizationCommand Plugin Actions public static final String GETLOCALENAME = "getLocaleName"; public static final String DATETOSTRING = "dateToString"; public static final String STRINGTODATE = "stringToDate"; public static final String GETDATEPATTERN = "getDatePattern"; public static final String GETDATENAMES = "getDateNames"; public static final String ISDAYLIGHTSAVINGSTIME = "isDayLightSavingsTime"; public static final String GETFIRSTDAYOFWEEK = "getFirstDayOfWeek"; public static final String NUMBERTOSTRING = "numberToString"; public static final String STRINGTONUMBER = "stringToNumber"; public static final String GETNUMBERPATTERN = "getNumberPattern"; public static final String GETCURRENCYPATTERN = "getCurrencyPattern"; public static final String GETPREFERREDLANGUAGE = "getPreferredLanguage"; //GlobalizationCommand Option Parameters public static final String OPTIONS = "options"; public static final String FORMATLENGTH = "formatLength"; //public static final String SHORT = "short"; //default for dateToString format public static final String MEDIUM = "medium"; public static final String LONG = "long"; public static final String FULL = "full"; public static final String SELECTOR = "selector"; //public static final String DATEANDTIME = "date and time"; //default for dateToString public static final String DATE = "date"; public static final String TIME = "time"; public static final String DATESTRING = "dateString"; public static final String TYPE = "type"; public static final String ITEM = "item"; public static final String NARROW = "narrow"; public static final String WIDE = "wide"; public static final String MONTHS = "months"; public static final String DAYS = "days"; //public static final String DECMIAL = "wide"; //default for numberToString public static final String NUMBER = "number"; public static final String NUMBERSTRING = "numberString"; public static final String PERCENT = "percent"; public static final String CURRENCY = "currency"; public static final String CURRENCYCODE = "currencyCode"; @Override public boolean execute(String action, JSONArray data, CallbackContext callbackContext) { JSONObject obj = new JSONObject(); try { if (action.equals(GETLOCALENAME)) { obj = getLocaleName(); } else if (action.equals(GETPREFERREDLANGUAGE)) { obj = getPreferredLanguage(); } else if (action.equalsIgnoreCase(DATETOSTRING)) { obj = getDateToString(data); } else if (action.equalsIgnoreCase(STRINGTODATE)) { obj = getStringtoDate(data); } else if (action.equalsIgnoreCase(GETDATEPATTERN)) { obj = getDatePattern(data); } else if (action.equalsIgnoreCase(GETDATENAMES)) { if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.GINGERBREAD) { throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR); } else { obj = getDateNames(data); } } else if (action.equalsIgnoreCase(ISDAYLIGHTSAVINGSTIME)) { obj = getIsDayLightSavingsTime(data); } else if (action.equalsIgnoreCase(GETFIRSTDAYOFWEEK)) { obj = getFirstDayOfWeek(data); } else if (action.equalsIgnoreCase(NUMBERTOSTRING)) { obj = getNumberToString(data); } else if (action.equalsIgnoreCase(STRINGTONUMBER)) { obj = getStringToNumber(data); } else if (action.equalsIgnoreCase(GETNUMBERPATTERN)) { obj = getNumberPattern(data); } else if (action.equalsIgnoreCase(GETCURRENCYPATTERN)) { obj = getCurrencyPattern(data); } else { return false; } callbackContext.success(obj); } catch (GlobalizationError ge) { callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, ge.toJson())); } catch (Exception e) { callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); } return true; } /* * @Description: Returns a well-formed ITEF BCP 47 language tag representing * the locale identifier for the client's current locale * * @Return: String: The BCP 47 language tag for the current locale */ private String toBcp47Language(Locale loc) { final char SEP = '-'; // we will use a dash as per BCP 47 String language = loc.getLanguage(); String region = loc.getCountry(); String variant = loc.getVariant(); // special case for Norwegian Nynorsk since "NY" cannot be a variant as per BCP 47 // this goes before the string matching since "NY" wont pass the variant checks if (language.equals("no") && region.equals("NO") && variant.equals("NY")) { language = "nn"; region = "NO"; variant = ""; } if (language.isEmpty() || !language.matches("\\p{Alpha}{2,8}")) { language = "und"; // Follow the Locale#toLanguageTag() implementation // which says to return "und" for Undetermined } else if (language.equals("iw")) { language = "he"; // correct deprecated "Hebrew" } else if (language.equals("in")) { language = "id"; // correct deprecated "Indonesian" } else if (language.equals("ji")) { language = "yi"; // correct deprecated "Yiddish" } // ensure valid country code, if not well formed, it's omitted if (!region.matches("\\p{Alpha}{2}|\\p{Digit}{3}")) { region = ""; } // variant subtags that begin with a letter must be at least 5 characters long if (!variant.matches("\\p{Alnum}{5,8}|\\p{Digit}\\p{Alnum}{3}")) { variant = ""; } StringBuilder bcp47Tag = new StringBuilder(language); if (!region.isEmpty()) { bcp47Tag.append(SEP).append(region); } if (!variant.isEmpty()) { bcp47Tag.append(SEP).append(variant); } return bcp47Tag.toString(); } /* * @Description: Returns the BCP 47 Unicode locale identifier for current locale setting * The locale is defined by a language code, a country code, and a variant, separated * by a hyphen, for example, "en-US", "fr-CA", etc., * * @Return: JSONObject * Object.value {String}: The locale identifier * * @throws: GlobalizationError.UNKNOWN_ERROR */ private JSONObject getLocaleName() throws GlobalizationError { JSONObject obj = new JSONObject(); try { obj.put("value", toBcp47Language(Locale.getDefault())); return obj; } catch (Exception e) { throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR); } } /* * @Description: Returns the BCP 47 language tag for the client's * current language. Currently in Android this is the same as locale, * since Java does not distinguish between locale and language. * * @Return: JSONObject * Object.value {String}: The language identifier * * @throws: GlobalizationError.UNKNOWN_ERROR */ private JSONObject getPreferredLanguage() throws GlobalizationError { JSONObject obj = new JSONObject(); try { obj.put("value", toBcp47Language(Locale.getDefault())); return obj; } catch (Exception e) { throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR); } } /* * @Description: Returns a date formatted as a string according to the client's user preferences and * calendar using the time zone of the client. * * @Return: JSONObject * Object.value {String}: The localized date string * * @throws: GlobalizationError.FORMATTING_ERROR */ private JSONObject getDateToString(JSONArray options) throws GlobalizationError { JSONObject obj = new JSONObject(); try { Date date = new Date((Long) options.getJSONObject(0).get(DATE)); //get formatting pattern from android device (Will only have device specific formatting for short form of date) or options supplied JSONObject datePattern = getDatePattern(options); SimpleDateFormat fmt = new SimpleDateFormat(datePattern.getString("pattern")); //return formatted date return obj.put("value", fmt.format(date)); } catch (Exception ge) { throw new GlobalizationError(GlobalizationError.FORMATTING_ERROR); } } /* * @Description: Parses a date formatted as a string according to the client's user * preferences and calendar using the time zone of the client and returns * the corresponding date object * @Return: JSONObject * Object.year {Number}: The four digit year * Object.month {Number}: The month from (0 - 11) * Object.day {Number}: The day from (1 - 31) * Object.hour {Number}: The hour from (0 - 23) * Object.minute {Number}: The minute from (0 - 59) * Object.second {Number}: The second from (0 - 59) * Object.millisecond {Number}: The milliseconds (from 0 - 999), not available on all platforms * * @throws: GlobalizationError.PARSING_ERROR */ private JSONObject getStringtoDate(JSONArray options) throws GlobalizationError { JSONObject obj = new JSONObject(); Date date; try { //get format pattern from android device (Will only have device specific formatting for short form of date) or options supplied DateFormat fmt = new SimpleDateFormat(getDatePattern(options).getString("pattern")); //attempt parsing string based on user preferences date = fmt.parse(options.getJSONObject(0).get(DATESTRING).toString()); //set Android Time object Time time = new Time(); time.set(date.getTime()); //return properties; obj.put("year", time.year); obj.put("month", time.month); obj.put("day", time.monthDay); obj.put("hour", time.hour); obj.put("minute", time.minute); obj.put("second", time.second); obj.put("millisecond", Long.valueOf(0)); return obj; } catch (Exception ge) { throw new GlobalizationError(GlobalizationError.PARSING_ERROR); } } /* * @Description: Returns a pattern string for formatting and parsing dates according to the client's * user preferences. * @Return: JSONObject * * Object.pattern {String}: The date and time pattern for formatting and parsing dates. * The patterns follow Unicode Technical Standard #35 * http://unicode.org/reports/tr35/tr35-4.html * Object.timezone {String}: The abbreviated name of the time zone on the client * Object.utc_offset {Number}: The current difference in seconds between the client's * time zone and coordinated universal time. * Object.dst_offset {Number}: The current daylight saving time offset in seconds * between the client's non-daylight saving's time zone * and the client's daylight saving's time zone. * * @throws: GlobalizationError.PATTERN_ERROR */ private JSONObject getDatePattern(JSONArray options) throws GlobalizationError { JSONObject obj = new JSONObject(); try { SimpleDateFormat fmtDate = (SimpleDateFormat) android.text.format.DateFormat .getDateFormat(this.cordova.getActivity()); //default user preference for date SimpleDateFormat fmtTime = (SimpleDateFormat) android.text.format.DateFormat .getTimeFormat(this.cordova.getActivity()); //default user preference for time String fmt = fmtDate.toLocalizedPattern() + " " + fmtTime.toLocalizedPattern(); //default SHORT date/time format. ex. dd/MM/yyyy h:mm a //get Date value + options (if available) if (options.getJSONObject(0).has(OPTIONS)) { //options were included JSONObject innerOptions = options.getJSONObject(0).getJSONObject(OPTIONS); //get formatLength option if (!innerOptions.isNull(FORMATLENGTH)) { String fmtOpt = innerOptions.getString(FORMATLENGTH); if (fmtOpt.equalsIgnoreCase(MEDIUM)) {//medium fmtDate = (SimpleDateFormat) android.text.format.DateFormat .getMediumDateFormat(this.cordova.getActivity()); } else if (fmtOpt.equalsIgnoreCase(LONG) || fmtOpt.equalsIgnoreCase(FULL)) { //long/full fmtDate = (SimpleDateFormat) android.text.format.DateFormat .getLongDateFormat(this.cordova.getActivity()); } } //return pattern type fmt = fmtDate.toLocalizedPattern() + " " + fmtTime.toLocalizedPattern(); if (!innerOptions.isNull(SELECTOR)) { String selOpt = innerOptions.getString(SELECTOR); if (selOpt.equalsIgnoreCase(DATE)) { fmt = fmtDate.toLocalizedPattern(); } else if (selOpt.equalsIgnoreCase(TIME)) { fmt = fmtTime.toLocalizedPattern(); } } } //TimeZone from users device //TimeZone tz = Calendar.getInstance(Locale.getDefault()).getTimeZone(); //substitute method TimeZone tz = TimeZone.getTimeZone(Time.getCurrentTimezone()); obj.put("pattern", fmt); obj.put("timezone", tz.getDisplayName(tz.inDaylightTime(Calendar.getInstance().getTime()), TimeZone.SHORT)); obj.put("utc_offset", tz.getRawOffset() / 1000); obj.put("dst_offset", tz.getDSTSavings() / 1000); return obj; } catch (Exception ge) { throw new GlobalizationError(GlobalizationError.PATTERN_ERROR); } } /* * @Description: Returns an array of either the names of the months or days of the week * according to the client's user preferences and calendar * @Return: JSONObject * Object.value {Array{String}}: The array of names starting from either * the first month in the year or the * first day of the week. * * @throws: GlobalizationError.UNKNOWN_ERROR */ @TargetApi(9) private JSONObject getDateNames(JSONArray options) throws GlobalizationError { JSONObject obj = new JSONObject(); //String[] value; JSONArray value = new JSONArray(); List<String> namesList = new ArrayList<String>(); final Map<String, Integer> namesMap; // final needed for sorting with anonymous comparator try { int type = 0; //default wide int item = 0; //default months //get options if available if (options.getJSONObject(0).length() > 0) { //get type if available if (!((JSONObject) options.getJSONObject(0).get(OPTIONS)).isNull(TYPE)) { String t = (String) ((JSONObject) options.getJSONObject(0).get(OPTIONS)).get(TYPE); if (t.equalsIgnoreCase(NARROW)) { type++; } //DateUtils.LENGTH_MEDIUM } //get item if available if (!((JSONObject) options.getJSONObject(0).get(OPTIONS)).isNull(ITEM)) { String t = (String) ((JSONObject) options.getJSONObject(0).get(OPTIONS)).get(ITEM); if (t.equalsIgnoreCase(DAYS)) { item += 10; } //Days of week start at 1 } } //determine return value int method = item + type; if (method == 1) { //months and narrow namesMap = Calendar.getInstance().getDisplayNames(Calendar.MONTH, Calendar.SHORT, Locale.getDefault()); } else if (method == 10) { //days and wide namesMap = Calendar.getInstance().getDisplayNames(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.getDefault()); } else if (method == 11) { //days and narrow namesMap = Calendar.getInstance().getDisplayNames(Calendar.DAY_OF_WEEK, Calendar.SHORT, Locale.getDefault()); } else { //default: months and wide namesMap = Calendar.getInstance().getDisplayNames(Calendar.MONTH, Calendar.LONG, Locale.getDefault()); } // save names as a list for (String name : namesMap.keySet()) { namesList.add(name); } // sort the list according to values in namesMap Collections.sort(namesList, new Comparator<String>() { public int compare(String arg0, String arg1) { return namesMap.get(arg0).compareTo(namesMap.get(arg1)); } }); // convert nameList into JSONArray of String objects for (int i = 0; i < namesList.size(); i++) { value.put(namesList.get(i)); } //return array of names return obj.put("value", value); } catch (Exception ge) { throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR); } } /* * @Description: Returns whether daylight savings time is in effect for a given date using the client's * time zone and calendar. * @Return: JSONObject * Object.dst {Boolean}: The value "true" indicates that daylight savings time is * in effect for the given date and "false" indicate that it is not. * * * @throws: GlobalizationError.UNKNOWN_ERROR */ private JSONObject getIsDayLightSavingsTime(JSONArray options) throws GlobalizationError { JSONObject obj = new JSONObject(); boolean dst = false; try { Date date = new Date((Long) options.getJSONObject(0).get(DATE)); //TimeZone tz = Calendar.getInstance(Locale.getDefault()).getTimeZone(); TimeZone tz = TimeZone.getTimeZone(Time.getCurrentTimezone()); dst = tz.inDaylightTime(date); //get daylight savings data from date object and user timezone settings return obj.put("dst", dst); } catch (Exception ge) { throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR); } } /* * @Description: Returns the first day of the week according to the client's user preferences and calendar. * The days of the week are numbered starting from 1 where 1 is considered to be Sunday. * @Return: JSONObject * Object.value {Number}: The number of the first day of the week. * * @throws: GlobalizationError.UNKNOWN_ERROR */ private JSONObject getFirstDayOfWeek(JSONArray options) throws GlobalizationError { JSONObject obj = new JSONObject(); try { int value = Calendar.getInstance(Locale.getDefault()).getFirstDayOfWeek(); //get first day of week based on user locale settings return obj.put("value", value); } catch (Exception ge) { throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR); } } /* * @Description: Returns a number formatted as a string according to the client's user preferences. * @Return: JSONObject * Object.value {String}: The formatted number string. * * @throws: GlobalizationError.FORMATTING_ERROR */ private JSONObject getNumberToString(JSONArray options) throws GlobalizationError { JSONObject obj = new JSONObject(); String value = ""; try { DecimalFormat fmt = getNumberFormatInstance(options);//returns Decimal/Currency/Percent instance value = fmt.format(options.getJSONObject(0).get(NUMBER)); return obj.put("value", value); } catch (Exception ge) { throw new GlobalizationError(GlobalizationError.FORMATTING_ERROR); } } /* * @Description: Parses a number formatted as a string according to the client's user preferences and * returns the corresponding number. * @Return: JSONObject * Object.value {Number}: The parsed number. * * @throws: GlobalizationError.PARSING_ERROR */ private JSONObject getStringToNumber(JSONArray options) throws GlobalizationError { JSONObject obj = new JSONObject(); Number value; try { DecimalFormat fmt = getNumberFormatInstance(options); //returns Decimal/Currency/Percent instance value = fmt.parse((String) options.getJSONObject(0).get(NUMBERSTRING)); return obj.put("value", value); } catch (Exception ge) { throw new GlobalizationError(GlobalizationError.PARSING_ERROR); } } /* * @Description: Returns a pattern string for formatting and parsing numbers according to the client's user * preferences. * @Return: JSONObject * Object.pattern {String}: The number pattern for formatting and parsing numbers. * The patterns follow Unicode Technical Standard #35. * http://unicode.org/reports/tr35/tr35-4.html * Object.symbol {String}: The symbol to be used when formatting and parsing * e.g., percent or currency symbol. * Object.fraction {Number}: The number of fractional digits to use when parsing and * formatting numbers. * Object.rounding {Number}: The rounding increment to use when parsing and formatting. * Object.positive {String}: The symbol to use for positive numbers when parsing and formatting. * Object.negative: {String}: The symbol to use for negative numbers when parsing and formatting. * Object.decimal: {String}: The decimal symbol to use for parsing and formatting. * Object.grouping: {String}: The grouping symbol to use for parsing and formatting. * * @throws: GlobalizationError.PATTERN_ERROR */ private JSONObject getNumberPattern(JSONArray options) throws GlobalizationError { JSONObject obj = new JSONObject(); try { //uses java.text.DecimalFormat to format value DecimalFormat fmt = (DecimalFormat) DecimalFormat.getInstance(Locale.getDefault()); //default format String symbol = String.valueOf(fmt.getDecimalFormatSymbols().getDecimalSeparator()); //get Date value + options (if available) if (options.getJSONObject(0).length() > 0) { //options were included if (!((JSONObject) options.getJSONObject(0).get(OPTIONS)).isNull(TYPE)) { String fmtOpt = (String) ((JSONObject) options.getJSONObject(0).get(OPTIONS)).get(TYPE); if (fmtOpt.equalsIgnoreCase(CURRENCY)) { fmt = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.getDefault()); symbol = fmt.getDecimalFormatSymbols().getCurrencySymbol(); } else if (fmtOpt.equalsIgnoreCase(PERCENT)) { fmt = (DecimalFormat) DecimalFormat.getPercentInstance(Locale.getDefault()); symbol = String.valueOf(fmt.getDecimalFormatSymbols().getPercent()); } } } //return properties obj.put("pattern", fmt.toPattern()); obj.put("symbol", symbol); obj.put("fraction", fmt.getMinimumFractionDigits()); obj.put("rounding", Integer.valueOf(0)); obj.put("positive", fmt.getPositivePrefix()); obj.put("negative", fmt.getNegativePrefix()); obj.put("decimal", String.valueOf(fmt.getDecimalFormatSymbols().getDecimalSeparator())); obj.put("grouping", String.valueOf(fmt.getDecimalFormatSymbols().getGroupingSeparator())); return obj; } catch (Exception ge) { throw new GlobalizationError(GlobalizationError.PATTERN_ERROR); } } /* * @Description: Returns a pattern string for formatting and parsing currency values according to the client's * user preferences and ISO 4217 currency code. * @Return: JSONObject * Object.pattern {String}: The currency pattern for formatting and parsing currency values. * The patterns follow Unicode Technical Standard #35 * http://unicode.org/reports/tr35/tr35-4.html * Object.code {String}: The ISO 4217 currency code for the pattern. * Object.fraction {Number}: The number of fractional digits to use when parsing and * formatting currency. * Object.rounding {Number}: The rounding increment to use when parsing and formatting. * Object.decimal: {String}: The decimal symbol to use for parsing and formatting. * Object.grouping: {String}: The grouping symbol to use for parsing and formatting. * * @throws: GlobalizationError.FORMATTING_ERROR */ private JSONObject getCurrencyPattern(JSONArray options) throws GlobalizationError { JSONObject obj = new JSONObject(); try { //get ISO 4217 currency code String code = options.getJSONObject(0).getString(CURRENCYCODE); //uses java.text.DecimalFormat to format value DecimalFormat fmt = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.getDefault()); //set currency format Currency currency = Currency.getInstance(code); fmt.setCurrency(currency); //return properties obj.put("pattern", fmt.toPattern()); obj.put("code", currency.getCurrencyCode()); obj.put("fraction", fmt.getMinimumFractionDigits()); obj.put("rounding", Integer.valueOf(0)); obj.put("decimal", String.valueOf(fmt.getDecimalFormatSymbols().getDecimalSeparator())); obj.put("grouping", String.valueOf(fmt.getDecimalFormatSymbols().getGroupingSeparator())); return obj; } catch (Exception ge) { throw new GlobalizationError(GlobalizationError.FORMATTING_ERROR); } } /* * @Description: Parses a JSONArray from user options and returns the correct Instance of Decimal/Percent/Currency. * @Return: DecimalFormat : The Instance to use. * * @throws: JSONException */ private DecimalFormat getNumberFormatInstance(JSONArray options) throws JSONException { DecimalFormat fmt = (DecimalFormat) DecimalFormat.getInstance(Locale.getDefault()); //default format try { if (options.getJSONObject(0).length() > 1) { //options were included if (!((JSONObject) options.getJSONObject(0).get(OPTIONS)).isNull(TYPE)) { String fmtOpt = (String) ((JSONObject) options.getJSONObject(0).get(OPTIONS)).get(TYPE); if (fmtOpt.equalsIgnoreCase(CURRENCY)) { fmt = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.getDefault()); } else if (fmtOpt.equalsIgnoreCase(PERCENT)) { fmt = (DecimalFormat) DecimalFormat.getPercentInstance(Locale.getDefault()); } } } } catch (JSONException je) { } return fmt; } }