Source

DateFactory.js

/*
 * DateFactory.js - Factory class to create the right subclasses of a date for any
 * calendar or locale.
 *
 * Copyright © 2012-2015, 2018, JEDLSoft
 *
 * Licensed 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.
 */

var ilib = require("../index.js");
var JSUtils = require("./JSUtils.js");

var Locale = require("./Locale.js");
var LocaleInfo = require("./LocaleInfo.js");
var JulianDay = require("./JulianDay.js");
var CalendarFactory = require("./CalendarFactory.js");

// Statically depend on these even though we don't use them
// to guarantee they are loaded into the cache already.
var IDate = require("./IDate.js");

/**
 * Factory method to create a new instance of a date subclass.<p>
 *
 * The options parameter can be an object that contains the following
 * properties:
 *
 * <ul>
 * <li><i>type</i> - specify the type/calendar of the date desired. The
 * list of valid values changes depending on which calendars are
 * defined. When assembling your iliball.js, include those date type
 * you wish to use in your program or web page, and they will register
 * themselves with this factory method. The "gregorian",
 * and "julian" calendars are all included by default, as they are the
 * standard calendars for much of the world. If not specified, the type
 * of the date returned is the one that is appropriate for the locale.
 * This property may also be given as "calendar" instead of "type".
 *
 * <li><i>onLoad</i> - a callback function to call when the date object is fully
 * loaded. When the onLoad option is given, the date factory will attempt to
 * load any missing locale data using the ilib loader callback.
 * When the constructor is done (even if the data is already preassembled), the
 * onLoad function is called with the current instance as a parameter, so this
 * callback can be used with preassembled or dynamic loading or a mix of the two.
 *
 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or
 * asynchronously. If this option is given as "false", then the "onLoad"
 * callback must be given, as the instance returned from this constructor will
 * not be usable for a while.
 *
 * <li><i>loadParams</i> - an object containing parameters to pass to the
 * loader callback function when locale data is missing. The parameters are not
 * interpretted or modified in any way. They are simply passed along. The object
 * may contain any property/value pairs as long as the calling code is in
 * agreement with the loader callback function as to what those parameters mean.
 * </ul>
 *
 * The options object is also passed down to the date constructor, and
 * thus can contain the the properties as the date object being instantiated.
 * See the documentation for {@link GregorianDate}, and other
 * subclasses for more details on other parameter that may be passed in.<p>
 *
 * Please note that if you do not give the type parameter, this factory
 * method will create a date object that is appropriate for the calendar
 * that is most commonly used in the specified or current ilib locale.
 * For example, in Thailand, the most common calendar is the Thai solar
 * calendar. If the current locale is "th-TH" (Thai for Thailand) and you
 * use this factory method to construct a new date without specifying the
 * type, it will automatically give you back an instance of
 * {@link ThaiSolarDate}. This is convenient because you do not
 * need to know which locales use which types of dates. In fact, you
 * should always use this factory method to make new date instances unless
 * you know that you specifically need a date in a particular calendar.<p>
 *
 * Also note that when you pass in the date components such as year, month,
 * day, etc., these components should be appropriate for the given date
 * being instantiated. That is, in our Thai example in the previous
 * paragraph, the year and such should be given as a Thai solar year, not
 * the Gregorian year that you get from the Javascript Date class. In
 * order to initialize a date instance when you don't know what subclass
 * will be instantiated for the locale, use a parameter such as "unixtime"
 * or "julianday" which are unambiguous and based on UTC time, instead of
 * the year/month/date date components. The date components for that UTC
 * time will be calculated and the time zone offset will be automatically
 * factored in.
 *
 * @static
 * @param {Object=} options options controlling the construction of this instance, or
 * undefined to use the default options
 * @return {IDate} an instance of a calendar object of the appropriate type
 */
var DateFactory = function(options) {
    var locale,
        type,
        sync = true,
        obj;

    if (options) {
        if (options.locale) {
            locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale;
        }

        type = options.type || options.calendar;

        if (typeof(options.sync) === 'boolean') {
            sync = options.sync;
        }
    }

    if (!locale) {
        locale = new Locale();    // default locale
    }

    if (!type) {
        new LocaleInfo(locale, {
            sync: sync,
            loadParams: options && options.loadParams,
            onLoad: function(info) {
                type = info.getCalendar();

                obj = DateFactory._init(type, options);
            }
        });
    } else {
        obj = DateFactory._init(type, options);
    }

    return obj
};

/**
 * Map calendar names to classes to initialize in the dynamic code model.
 * TODO: Need to figure out some way that this doesn't have to be updated by hand.
 * @private
 */
DateFactory._dynMap = {
    "coptic":       "Coptic",
    "ethiopic":     "Ethiopic",
    "gregorian":    "Gregorian",
    "han":          "Han",
    "hebrew":       "Hebrew",
    "islamic":      "Islamic",
    "julian":       "Julian",
    "persian":      "Persian",
    "persian-algo": "PersianAlgo",
    "thaisolar":    "ThaiSolar"
};

function circumventWebPackDate(x) {
    return "./" + x + "Date.js";
}

function circumventWebPackCal(x) {
    return "./" + x + "Cal.js";
}

/**
 * Dynamically load the code for a calendar and calendar class if necessary.
 * @protected
 */
DateFactory._dynLoadDate = function (name, fnc) {
    if (!IDate._constructors[name]) {
        var entry = DateFactory._dynMap[name];
        if (entry) {
            // eslint-disable-next-line
            IDate._constructors[name] = require(fnc(entry));
        }
    }
    return IDate._constructors[name];
};

/**
 * @protected
 * @static
 */
DateFactory._init = function(type, options) {
    var cons;

    if (ilib.isDynCode()) {
        DateFactory._dynLoadDate(type, circumventWebPackDate);
        CalendarFactory._dynLoadCalendar(type, circumventWebPackCal);
    }

    cons = IDate._constructors[type];

    // pass the same options through to the constructor so the subclass
    // has the ability to do something with if it needs to
    if (!cons && options && typeof(options.onLoad) === "function") {
        options.onLoad(undefined);
    }
    return cons && new cons(options);
};

/**
 * Convert JavaScript Date objects and other types into native Dates. This accepts any
 * string or number that can be translated by the JavaScript Date class,
 * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse)
 * any JavaScript Date classed object, any IDate subclass, an JulianDay object, an object
 * containing the normal options to initialize an IDate instance, or null (will
 * return null or undefined if input is null or undefined). Normal output is
 * a standard native subclass of the IDate object as appropriate for the locale.
 *
 * @static
 * @protected
 * @param {IDate|Object|JulianDay|Date|string|number=} inDate The input date object, string or Number.
 * @param {IString|string=} timezone timezone to use if a new date object is created
 * @param {Locale|string=} locale locale to use when constructing an IDate
 * @return {IDate|null|undefined} an IDate subclass equivalent to the given inDate
 */
DateFactory._dateToIlib = function(inDate, timezone, locale) {
    if (typeof(inDate) === 'undefined' || inDate === null) {
        return inDate;
    }
    if (inDate instanceof IDate) {
        return inDate;
    }
    if (typeof(inDate) === 'number') {
        return DateFactory({
            unixtime: inDate,
            timezone: timezone,
            locale: locale
        });
    }
    if (typeof(inDate) === 'string') {
        inDate = new Date(inDate);
    }
    if (JSUtils.isDate(inDate)) {
        return DateFactory({
            unixtime: inDate.getTime(),
            timezone: timezone,
            locale: locale
        });
    }
    if (inDate instanceof JulianDay) {
        return DateFactory({
            jd: inDate,
            timezone: timezone,
            locale: locale
        });
    }
    if (typeof(inDate) === 'object') {
        return DateFactory(inDate);
    }
    return DateFactory({
        unixtime: inDate.getTime(),
        timezone: timezone,
        locale: locale
    });
};

DateFactory._ilibToDate = function(ilibDate, timezone, locale) {
    if (typeof(ilibDate) !== 'object' || !(ilibDate instanceof IDate)) {
        return ilibDate;
    }

    return new Date(ilibDate.getTimeExtended());
};

module.exports = DateFactory;