diff --git a/CHANGELOG.md b/CHANGELOG.md index a8d785cf63..b5e1ad300d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v0.10.0 (13 Oct 2014) + +* Additions: + - `format` + - `startOfYear` + ## v0.9.0 (10 Oct 2014) * Additions: diff --git a/package.json b/package.json index 20f09c71dc..d7d0452a97 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "date-fns", - "version": "0.9.0", + "version": "0.10.0", "author": "Sasha Koss ", "description": "Date helpers", "repository": "https://github.com/kossnocorp/date-fns", diff --git a/src/__tests__/format_test.js b/src/__tests__/format_test.js new file mode 100644 index 0000000000..e00650e7b7 --- /dev/null +++ b/src/__tests__/format_test.js @@ -0,0 +1,125 @@ +var format = require('../format'); + +describe('format', function() { + beforeEach(function () { + this._date = new Date(1986, 3, 4, 10, 32, 0, 900); + }); + + it('simple YY', function() { + var b = new Date(2009, 1, 14, 15, 25, 50, 125); + expect(format(b, 'YY')).to.equal('09'); + }); + + it('accepts string as a date', function() { + expect(format('2014-04-04', 'YYYY-MM-DD')).to.be.equal('2014-04-04'); + }); + + it('return default ISO string format if format is unknown', function() { + expect(format(this._date)).to.be.equal('1986-04-04T10:32:00.900Z'); + }); + + describe('format escape brackets', function() { + it('should ignore escaped chars that in [] brackets', function() { + var result = format(this._date, '[not a date] MM'); + expect(result).to.be.equal('not a date 04'); + }); + }); + + describe('ordinal', function() { + it('should return 1st', function() { + var date = format(this._date, 'Do of t[h][e] Mo in YYYY'); + expect(date).to.be.equal('4th of the 4th in 1986'); + }); + }); + + describe('Months', function() { + it('return months names', function() { + var date = format(this._date, 'MMM MMMM'); + expect(date).to.equal('Apr April'); + }); + it('return months names reverse parse', function() { + var date = format(this._date, 'MMMM MMM'); + expect(date).to.equal('April Apr'); + }); + it('all month variants', function() { + var date = format(this._date, 'M Mo MM MMM MMMM'); + expect(date).to.equal('4 4th 04 Apr April'); + }); + }); + + describe('formatting day', function() { + describe('with DDD', function() { + context('for first day of a year', function() { + it('returns correct day number', function() { + var result = format(new Date(1992, 0, 1, 0, 0, 0, 0), 'DDD'); + expect(result).to.be.equal('1'); + }); + }); + + context('for last day of a common year', function() { + it('returns correct day number', function() { + var lastDay = format(new Date(1986, 11, 31, 23, 59, 59, 999), 'DDD'); + expect(lastDay).to.be.equal('365'); + }); + }); + + context('for last day of a leap year', function() { + it('returns correct day number', function() { + var result = format(new Date(1992, 11, 31, 23, 59, 59, 999), 'DDD'); + expect(result).to.be.equal('366'); + }); + }); + }); + + it('return ordinal day of the year', function() { + var firstDay = format(new Date(1992, 0, 1, 0, 0, 0, 0), 'DDDo'); + expect(firstDay).to.be.equal('1st'); + }); + + it('return zero filled day of year', function() { + var firstDay = format(new Date(1992, 0, 1, 0, 0, 0, 0), 'DDDD'); + expect(firstDay).to.be.equal('001'); + }); + }); + + describe('Quartal', function() { + it('right quartal', function() { + var result = []; + for (var i = 0; i != 12; i++){ + result.push(format(new Date(1986, i, 1), 'Q')); + } + expect(result).to.deep.equal( + ['1','1','1', '2', '2', '2', '3', '3', '3', '4', '4', '4'] + ); + }); + }); + + describe('day of week', function() { + it('display', function() { + var result = format(this._date, 'd do dd ddd dddd'); + expect(result).to.be.equal('5 5th Fr Fri Friday'); + }); + + it('ISO', function() { + expect(format(this._date, 'E')).to.be.equal('6'); + }); + + it('parses ok for different variants', function() { + var firstDay = format(this._date, 'dddd ddd d do [d] do dd ddd dddd'); + expect(firstDay).to.be.equal('Friday Fri 5 5th d 5th Fr Fri Friday'); + }); + }); + + describe('hours', function() { + it('am/pm', function() { + expect(format(this._date, 'hh:mm a')).to.be.equal('10:32 am'); + }); + }); + + describe('seconds', function() { + it('show', function() { + expect(format(this._date, 's ss')).to.be.equal('0 00'); + }); + }); +}); + diff --git a/src/__tests__/start_of_year_test.js b/src/__tests__/start_of_year_test.js new file mode 100644 index 0000000000..0c603caa57 --- /dev/null +++ b/src/__tests__/start_of_year_test.js @@ -0,0 +1,26 @@ +var startOfYear = require('../start_of_year'); + +describe('startOfYear', function() { + it('returns date with time setted to 00:00:00', function() { + var date = new Date(2014, 8, 2, 11, 55, 00); + var result = startOfYear(date); + expect(result).to.be.eql( + new Date(2014, 0, 1, 0, 0, 0, 0) + ); + }); + + it('accepts string', function() { + var date = new Date(2014, 8, 2, 11, 55, 00); + var result = startOfYear(date.toISOString()); + expect(result).to.be.eql( + new Date(2014, 0, 1, 0, 0, 0, 0) + ); + }); + + it('do not mutates original date', function() { + var date = new Date('2014-09-02T11:55:00'); + startOfYear(date); + expect(date).to.be.eql(new Date('2014-09-02T11:55:00')); + }); +}); + diff --git a/src/format.js b/src/format.js new file mode 100644 index 0000000000..fdee2af3d8 --- /dev/null +++ b/src/format.js @@ -0,0 +1,184 @@ +var startOfDay = require('./start_of_day'); +var startOfYear = require('./start_of_year'); + +var NUMBER_OF_MS_IN_DAY = 864e5; + +/** + * Returns formatted date string in a given format + * @param {date|string} date + * @param {string} format + * @returns {string} + */ +var format = function(date, format) { + date = date instanceof Date ? date : new Date(date); + + if (!format) { + format = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; + }; + + var formatFunction = makeFormatFunction(format); + return formatFunction(date); +}; + +var formats = { + 'M': function() { + return this.getMonth() + 1; + }, + 'MM': function() { + return leftZeroFill(this.getMonth() + 1, 2); + }, + 'MMM': function() { + return locale.monthsShort[this.getMonth()]; + }, + 'MMMM': function() { + return locale.months[this.getMonth()]; + }, + 'Q': function() { + return Math.ceil((this.getMonth() + 1) / 3); + }, + 'D': function() { + return this.getDate(); + }, + 'DD': function() { + return leftZeroFill(this.getDate(), 2); + }, + 'DDD': function() { + var diffWithStartOfYear = + startOfDay(this).getTime() - startOfYear(this).getTime(); + return diffWithStartOfYear / NUMBER_OF_MS_IN_DAY + 1; + }, + 'DDDD': function() { + return leftZeroFill(formats['DDD'].apply(this), 3); + }, + 'd': function() { + return this.getDay(); + }, + 'dd': function() { + return locale.dayNamesMin[this.getDay()]; + }, + 'ddd': function() { + return locale.dayNamesShort[this.getDay()]; + }, + 'dddd': function() { + return locale.dayNames[this.getDay()]; + }, + 'E': function() { + return this.getDay() + 1; + }, + 'YY': function() { + return String(this.getFullYear()).substr(2); + }, + 'YYYY': function() { + return this.getFullYear() + }, + 'A': function() { + return (this.getHours() / 12) >= 1 ? 'PM' : 'AM'; + }, + 'a': function() { + return (this.getHours() / 12) >= 1 ? 'pm' : 'am'; + }, + 'H': function() { + return this.getHours(); + }, + 'HH': function() { + return leftZeroFill(this.getHours(), 2); + }, + 'h': function() { + return this.getHours() % 12; + }, + 'hh': function() { + return leftZeroFill(this.getHours() % 12, 2); + }, + 'm': function() { + return this.getMinutes(); + }, + 'mm': function() { + return leftZeroFill(this.getMinutes()); + }, + 's': function() { + return this.getSeconds(); + }, + 'ss': function() { + return leftZeroFill(this.getSeconds(), 2); + }, + 'S': function() { + return this.getMilliseconds(); + }, + 'SS': function() { + return leftZeroFill(this.getMilliseconds(), 2); + }, + 'SSS': function() { + return leftZeroFill(this.getMilliseconds(), 3); + } +}; + +var ordinalFunctions = ['M', 'D', 'DDD', 'd']; +ordinalFunctions.forEach(function(functionName){ + formats[functionName + 'o'] = function() { + return locale.ordinal(formats[functionName].apply(this)); + }; +}); + +var formattingTokens = Object.keys(formats).sort().reverse(); +var formattingTokensRegexp = new RegExp( + '(\\[[^\\[]*\\])|(\\\\)?' + '(' + formattingTokens.join('|') + '|.)', 'g' +); + +var makeFormatFunction = function(format) { + var array = format.match(formattingTokensRegexp), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formats[array[i]]) { + array[i] = formats[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function(mom) { + var output = ''; + for (i = 0; i < length; i++) { + if (array[i] instanceof Function) { + output += array[i].call(mom, format); + } else { + output += array[i]; + } + } + return output; + }; +}; + +var removeFormattingTokens = function(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); + } + return input.replace(/\\/g, ''); +}; + +var leftZeroFill = function(number, targetLength) { + var output = '' + Math.abs(number); + + while (output.length < targetLength) { + output = '0' + output; + } + return output; +}; + +var locale = { + ordinal: function(number) { + var b = number % 10, + output = (+(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'] +}; + +module.exports = format; + diff --git a/src/start_of_year.js b/src/start_of_year.js new file mode 100644 index 0000000000..ede03b8ffd --- /dev/null +++ b/src/start_of_year.js @@ -0,0 +1,13 @@ +/** + * Returns start of a year for given date. Date will be in local timezone. + * @param {date|string} dirtyDate + * @returns {date} + */ +var startOfYear = function(dirtyDate) { + var cleanDate = new Date(dirtyDate); + var date = new Date(cleanDate.getFullYear(), 0, 1, 0, 0, 0, 0); + return date; +}; + +module.exports = startOfYear; +