Skip to content
This repository has been archived by the owner on Jan 26, 2020. It is now read-only.

New date formattig algorithm #176

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions spec/date.new-formatting.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
var TestUtils = require('./test-utils');
var timezoneJS = TestUtils.getTimezoneJS({
newFormatting: {
enabled: true
}
});
function createDate(year, month, day, hour, minute, timeZoneName) {
month = month - 1; //because month in timezoneJS begins from 0
var date = new timezoneJS.Date(year, month, day, hour, minute, 0, 0, timeZoneName);
return date;
}
var TZ_GMT = "Etc/GMT+1";
var TZ_EUROPE_MOSCOW = "Europe/Moscow";
var DEFAULT_MASK = "dd.MM.yyyy HH:mm:ss.SSS";
var DEFAULT_DATE = createDate(2015, 12, 14, 8, 20, TZ_EUROPE_MOSCOW);
var DEFAULT_DATE_AS_STRING = "14.12.2015 08:20:00.000";

describe('timezoneJS.Date new formatting', function () {
it('new formatting code should not impact not changed part of formatting code', function () {
expect(DEFAULT_DATE.toString(DEFAULT_MASK)).toEqual(DEFAULT_DATE_AS_STRING);
});

it('time zone literals (z, Z, X)', function () {
var tzLiterals = ["z", "zz", "zzz", "zzzz", "Z", "ZZ", "X", "XX", "XXX", "XXXX"];
var tzERs = ["MSK", "MSK", "MSK", TZ_EUROPE_MOSCOW, "+0300", "+0300", "+03", "+0300", "+03:00", "+03:00"];
for (var i = 0; i < tzLiterals.length; ++i) {
expect(DEFAULT_DATE.toString(DEFAULT_MASK + " " + tzLiterals[i])).toEqual(DEFAULT_DATE_AS_STRING + " " + tzERs[i]);
}
});

it('time zone literals (z, Z, X) in case of GMT', function () {
var date = createDate(2015, 12, 14, 8, 20, TZ_GMT)
var tzLiterals = ["z", "zz", "zzz", "zzzz", "Z", "ZZ", "X", "XX", "XXX", "XXXX"];
var tzERs = ["GMT+1", "GMT+1", "GMT+1", TZ_GMT, "+0100", "+0100", "+01", "+0100", "+01:00", "+01:00"];
for (var i = 0; i < tzLiterals.length; ++i) {
expect(date.toString(DEFAULT_MASK + " " + tzLiterals[i])).toEqual(DEFAULT_DATE_AS_STRING + " " + tzERs[i]);
}
});

it("user should not add non date literals outside of ''", function () {
expect(function(){DEFAULT_DATE.toString(DEFAULT_MASK + " q")})
.toThrow(new Error("Illegal format \"" + DEFAULT_MASK + " q" + "\". It contains non date literals (" + "q" + ") outside of ''"));
});

it("escaping text in ''", function () {
expect(DEFAULT_DATE.toString(DEFAULT_MASK + "'escape text'")).toEqual(DEFAULT_DATE_AS_STRING + "escape text");
expect(DEFAULT_DATE.toString(DEFAULT_MASK + "'a''''b''c'")).toEqual(DEFAULT_DATE_AS_STRING + "a''b'c");
expect(DEFAULT_DATE.toString(DEFAULT_MASK + "'zzz'")).toEqual(DEFAULT_DATE_AS_STRING + "zzz");
expect(DEFAULT_DATE.toString(DEFAULT_MASK + " _ __ _")).toEqual(DEFAULT_DATE_AS_STRING + " _ __ _");
});
});
142 changes: 137 additions & 5 deletions src/date.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@
});
};

// private constants for timezoneJS.Date
var DATE_LITERALS = "aydmsShMkHEZzX";
var NON_DATELITERALS_REGEXP = new RegExp("[^" + DATE_LITERALS + "_\\W]","g");

// Constructor, which is similar to that of the native Date object itself
timezoneJS.Date = function () {
if(this === timezoneJS) {
Expand Down Expand Up @@ -504,7 +508,27 @@
_this.setTimezone(tz);
}
var hours = _this.getHours();
return result

if (globalOptions.newFormatting.enabled) {
//check that user not add non date literals outside of ''
var check = result.replace(/'.*'/g, ""); //replace all text in ''
var found = check.match(NON_DATELITERALS_REGEXP);
if (found) {
throw new Error("Illegal format \"" + format + "\". It contains non date literals (" + found.join() + ") outside of ''");
}
}

var escapeParts = [];
if (globalOptions.newFormatting.enabled) {
//escape text in '' (part1)
result = result.replace(/'.*'/g, function (str) {
var escapePart = str.substr(1, str.length - 2).replace(/''/g, "'");
escapeParts.push(escapePart);
return "'" + (escapeParts.length - 1) + "'"; //see continue in bottom code in this function
});
}

result = result
// fix the same characters in Month names
.replace(/a+/g, function () { return 'k'; })
// `y`: year
Expand Down Expand Up @@ -543,9 +567,88 @@
// `H`: hour
.replace(/H+/g, function (token) { return _fixWidth(hours, token.length); })
// `E`: day
.replace(/E+/g, function (token) { return DAYS[_this.getDay()].substring(0, token.length); })
// `Z`: timezone abbreviation
.replace(/Z+/gi, function () { return tzInfo.tzAbbr; });
.replace(/E+/g, function (token) { return DAYS[_this.getDay()].substring(0, token.length); });
if (!globalOptions.newFormatting.enabled) {
// `Z`: timezone abbreviation
result = result.replace(/Z+/gi, function () { return tzInfo.tzAbbr; });
} else {
//According to http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html
//z - General time zone, Z - RFC 822 time zone, X - ISO 8601 time zone
//Example of 'Europe/Moscow' should be formatted like
//z = zz = zzz = MSK; zzzz = zzzz... = Moscow Standard Time
//Z = Z... = +0300 = XX
//X = +03 XX = +0300 XXX = +03:00 XXXX... = error
//Assumptions:
//1) Unfortunately there is no full names for time zones in Olson time zone files (e.g. Moscow Standard Time for MSK)
//so decide to display TZ id (e.g. Europe/Moscow)
//2) XXXX... = error, but decided to obtain normally like XXX
result = result
//zzzz...
.replace(/zzzz+/g, function () {
return tzInfo.tzId;
})
//z, zz, zzz
.replace(/z{1,3}/g, function () {
return tzInfo.tzAbbr;
})
//Z...
.replace(/Z+/g, function () {
return _this.offsetToString(tzInfo, true);
})
//XXX...
.replace(/XXX+/g, function () {
return _this.offsetToString(tzInfo, true, true);
})
//XX
.replace(/XX/g, function () {
return _this.offsetToString(tzInfo, true);
})
//X
.replace(/X/g, function () {
return _this.offsetToString(tzInfo);
});
}

if (globalOptions.newFormatting.enabled) {
//escape text in '' (part2)
result = result.replace(/'.*'/g, function (str) {
var index = parseInt(str.replace(/'/g, ""));
return escapeParts[index];
});
}
return result;
},
offsetToString: function (tzInfo, showMinute, showDelimiter) {
var offset = tzInfo.tzOffset;
var result = "";

//Offset sign is opposite to sign of UTC+X. But Etc/GMT time zones have not opposite sign. Also some system can
//change sign in Olson time zone files of Etc/GMT for compatibility with old format.
//See comments in file 'etcetera' in Olson time zone files for more info.
//So there is ability to impact on offsetToString result by
//timezoneJS.timezone.init({newFormatting: {offset: {revertSign: true, revertSignForGMT: true}}});
if (tzInfo.tzId.lastIndexOf("Etc/", 0) === 0) {
if(globalOptions.newFormatting.offset.revertSignForGMT) {
result += (offset < 0) ? "+" : "-";
} else {
result += (offset < 0) ? "-" : "+";
}
} else if (globalOptions.newFormatting.offset.revertSign) {
result += (offset < 0) ? "+" : "-";
}

offset = Math.abs(offset);
var hh = Math.floor(offset / 60) + "";
if (hh.length == 1) hh = "0" + hh;

var mm = "";
if (showMinute) {
mm = offset % 60 + "";
if (mm.length == 1) mm = "0" + mm;
}

result += hh + (showDelimiter ? ":" : "") + mm;
return result;
},
toUTCString: function () { return this.toGMTString(); },
civilToJulianDayNumber: function (y, m, d) {
Expand All @@ -571,6 +674,32 @@
}
};

var globalOptions = {
newFormatting: {
//compatibility for /Z+/gi in toString()
enabled: false,
//options for timezoneJS.Date.offsetToString
offset: {
revertSign: true,
revertSignForGMT: false
}
},
setOptions: function(options) {
this._setOptions(this, options);
},
_setOptions: function(toOptions, fromOptions) {
//use only existed keys from globalOptions and override values in globalOptions from fromOptions if it contains key
for (var key in toOptions) {
if (!(toOptions[key] instanceof Function) && key in fromOptions) {
if (toOptions[key] instanceof Object) {
this._setOptions(toOptions[key], fromOptions[key]);
} else {
toOptions[key] = fromOptions[key];
}
}
}
}
};

timezoneJS.timezone = new function () {
var _this = this
Expand Down Expand Up @@ -908,6 +1037,8 @@
this.rules = {};

this.init = function (o) {
globalOptions.setOptions(o);

var opts = { async: true }
, def = this.loadingScheme === this.loadingSchemes.PRELOAD_ALL
? this.zoneFiles
Expand Down Expand Up @@ -1062,7 +1193,8 @@
off = getAdjustedOffset(off, rule[6]);
}
var abbr = getAbbreviation(z, rule);
return { tzOffset: off, tzAbbr: abbr };
//unfortunately there is no full name of TZ in Olson tz files
return { tzOffset: off, tzAbbr: abbr, tzId: tz };
};
//Lazy-load any zones not yet loaded.
this.lazyLoadZoneFiles = function(tz) {
Expand Down