bali-component-framework
Version:
This library provides a JavaScript based implementation of the Bali Nebula™ Component Framework.
271 lines (234 loc) • 9.31 kB
JavaScript
/************************************************************************
* Copyright (c) Crater Dog Technologies(TM). All Rights Reserved. *
************************************************************************
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. *
* *
* This code is free software; you can redistribute it and/or modify it *
* under the terms of The MIT License (MIT), as published by the Open *
* Source Initiative. (See http://opensource.org/licenses/MIT) *
************************************************************************/
;
/*
* This element class captures the state and methods associated with a moment
* in time.
*/
const moduleName = '/bali/elements/Moment';
const utilities = require('../utilities');
const abstractions = require('../abstractions');
const Duration = require('./Duration').Duration;
const FORMATS = [
'Y',
'Y-MM',
'Y-MM-DD',
'Y-MM-DDTHH',
'Y-MM-DDTHH:mm',
'Y-MM-DDTHH:mm:ss',
'Y-MM-DDTHH:mm:ss.SSS'
];
// PUBLIC FUNCTIONS
/**
* This function creates a new moment in time using the specified value and parameters.
*
* An optional debug argument may be specified that controls the level of debugging that
* should be applied during execution. The allowed levels are as follows:
* <pre>
* 0: no debugging is applied (this is the default value and has the best performance)
* 1: log any exceptions to console.error before throwing them
* 2: perform argument validation checks on each call (poor performance)
* 3: log interesting arguments, states and results to console.log
* </pre>
*
* @param {String|Number} value The optional source string value or millisecond value of
* the moment in time.
* @param {Object} parameters Optional parameters used to parameterize this element.
* @returns {Moment} The new moment in time.
*/
const Moment = function(value, parameters, debug) {
abstractions.Element.call(
this,
[ moduleName ],
[ ],
parameters,
debug
);
if (this.debug > 1) {
this.validateArgument('$Moment', '$value', value, [
'/javascript/Undefined',
'/javascript/String',
'/javascript/Number'
]);
}
var format = FORMATS[6];
if (value === null || value === undefined) { // can't check for zero since that is a valid date
value = Date.now(); // current time in milliseconds since EPOC
} else if (typeof value === 'string') {
format = detectFormat(value, this.debug);
value = extractUTCValue(value, this.debug);
}
// since this element is immutable the attributes must be read-only
this.getValue = function() {
return value;
};
this.getFormat = function() {
return format;
};
this.getMillisecond = function() {
return new Date(value).getUTCMilliseconds();
};
this.getSecond = function() {
return new Date(value).getUTCSeconds();
};
this.getMinute = function() {
return new Date(value).getUTCMinutes();
};
this.getHour = function() {
return new Date(value).getUTCHours();
};
this.getDay = function() {
return new Date(value).getUTCDate();
};
this.getMonth = function() {
return new Date(value).getUTCMonth() + 1;
};
this.getYear = function() {
return new Date(value).getUTCFullYear();
};
return this;
};
Moment.prototype = Object.create(abstractions.Element.prototype);
Moment.prototype.constructor = Moment;
exports.Moment = Moment;
// PUBLIC METHODS
/**
* This method returns an ISO 8601 formatted string for this moment.
*
* @returns {String} The ISO 8601 formatted string.
*/
Moment.prototype.toISOString = function() {
var string = this.getFormat();
string = string.replace('Y', '' + this.getYear());
string = string.replace('MM', ('0' + this.getMonth()).slice(-2));
string = string.replace('DD', ('0' + this.getDay()).slice(-2));
string = string.replace('HH', ('0' + this.getHour()).slice(-2));
string = string.replace('mm', ('0' + this.getMinute()).slice(-2));
string = string.replace('ss', ('0' + this.getSecond()).slice(-2));
string = string.replace('SSS', ('00' + this.getMillisecond()).slice(-3));
return string;
};
// PUBLIC FUNCTIONS
/**
* This function returns the duration between two moments in time.
*
* @param {Moment} first The first moment in time.
* @param {Moment} second The second moment in time.
* @param {Number} debug A number in the range 0..3.
* @returns {Duration} The duration between the two moments in time.
*/
Moment.duration = function(first, second, debug) {
debug = debug || 0; // default value
if (debug > 1) {
abstractions.Component.validateArgument(moduleName, '$duration', '$first', first, [
'/bali/elements/Moment'
]);
abstractions.Component.validateArgument(moduleName, '$duration', '$second', second, [
'/bali/elements/Moment'
]);
}
return new Duration(first.getValue() - second.getValue(), undefined, debug);
};
/**
* This function returns a moment in time that is earlier than the specified moment
* by the specified duration of time.
*
* @param {Moment} moment The initial moment in time.
* @param {Duration} duration The duration of time to be subtracted.
* @param {Number} debug A number in the range 0..3.
* @returns {Moment} The resulting moment in time.
*/
Moment.earlier = function(moment, duration, debug) {
if (debug > 1) {
abstractions.Component.validateArgument(moduleName, '$earlier', '$moment', moment, [
'/bali/elements/Moment'
]);
abstractions.Component.validateArgument(moduleName, '$earlier', '$duration', duration, [
'/bali/elements/Duration'
]);
}
const earlier = moment.getValue() - duration.getValue();
return new Moment(earlier, moment.getParameters(), debug);
};
/**
* This function returns a moment in time that is later than the specified moment
* by the specified duration of time.
*
* @param {Moment} moment The initial moment in time.
* @param {Duration} duration The duration of time to be added.
* @param {Number} debug A number in the range 0..3.
* @returns {Moment} The resulting moment in time.
*/
Moment.later = function(moment, duration, debug) {
if (debug > 1) {
abstractions.Component.validateArgument(moduleName, '$later', '$moment', moment, [
'/bali/elements/Moment'
]);
abstractions.Component.validateArgument(moduleName, '$later', '$duration', duration, [
'/bali/elements/Duration'
]);
}
const later = moment.getValue() + duration.getValue();
return new Moment(later, moment.getParameters(), debug);
};
// PRIVATE FUNCTIONS
const two = '(\\d\\d)';
const three = '(\\d\\d\\d)';
const any = '(-?\\d+)';
const moment = `${any}(?:-${two})?(?:-${two})?(?:T${two})?(?::${two})?(?::${two})?(?:[\\.,]${three})?`;
const pattern = new RegExp(moment);
const parseISOString = function(string, debug) {
const fields = string.match(pattern).slice(1); // remove the first matching group which is the entire string
var index = fields.indexOf(undefined); // index of the first undefined field
if (index === 0) {
const exception = new abstractions.Exception({
$module: moduleName,
$procedure: '$parseISOString',
$exception: '$invalidFormat',
$string: string,
$text: '"The string is not in a valid ISO 8601 timestamp format."'
}, undefined, debug);
throw exception;
}
if (index < 0) index = 7; // all fields are defined
return fields.slice(0, index); // remove any undefined fields
};
const detectFormat = function(string, debug) {
const fields = parseISOString(string, debug);
return FORMATS[fields.length - 1];
};
const extractUTCValue = function(string, debug) {
const fields = parseISOString(string, debug);
var utcString = '';
var year = fields[0];
if (year.length === 4) {
// normal four digit format: dddd
utcString += year;
} else {
// extended signed six digit format: [+-]dddddd
var sign = '+'; // default is positive (not specified in language grammar so won't happen
if (year[0] === '-') {
sign = '-';
year = year.slice(1); // remove the sign
}
// pad the year with leading zeros to make exactly signed six digits
year = ('00000' + year).slice(-6);
utcString += sign + year;
}
utcString += '-' + (fields[1] || '01'); // append the month
utcString += '-' + (fields[2] || '01'); // append the day
utcString += 'T';
utcString += (fields[3] || '00') + ':'; // append the hour
utcString += (fields[4] || '00') + ':'; // append the minute
utcString += (fields[5] || '00') + '.'; // append the second
utcString += (fields[6] || '000'); // append the milliseconds
utcString += 'Z'; // must have UTC symbol or it will treat it is local
return Date.parse(utcString); // returns the milliseconds since EPOC
};