From 72b57342249670732f7564bd32191f785022b53d Mon Sep 17 00:00:00 2001 From: kinergy Date: Fri, 3 Jan 2014 01:00:40 -0700 Subject: [PATCH] Military (24-hour) time support; toISOString() returns ISO8601 time component (without seconds) --- README.md | 25 ++++++++++++++++--- test/format.js | 43 +++++++++++++++++++++++++++++--- test/parsing.js | 40 ++++++++++++++++++++++++------ time.js | 65 +++++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 152 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index f8ced67..cd13d1e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ with the option to convert to the next immediate corresponding Date. Built for [Promt](http://promtapp.com), to solve [this problem](http://stackoverflow.com/q/141348/962091). **Browser** - + ``` $ bower install time-js # or just manually download time.js ``` @@ -17,6 +17,7 @@ $ bower install time-js # or just manually download time.js ```js var t = Time('2p'); t.hours(); // 2 +t.militaryHours(); // 14 t.minutes(); // 0 t.period(); // 'pm' t.toString(); // '2:00 pm' @@ -45,6 +46,7 @@ Parses strings such as "8:20" into a Date-less Time. ```js new Time('1') // 1:00 +new Time('13') // 1:00 pm new Time('1:23') // 1:23 ``` @@ -77,7 +79,7 @@ Does validation statically... ```js Time.isValid('8:00') // true Time.isValid('12:60') // false -Time.isValid('13:23') // false +Time.isValid('13:23') // true ``` ... or after construction. @@ -92,6 +94,7 @@ There's basic formatting ```js Time('2:30p').format('hh:mm A'); // '02:30 P' +Time('2:30p').format('HH:mm'); // '14:30' Time('12 am').format('h: p'); // '12 a' Time('220 a').format('h: p'); // '2:20 a' Time('7').format('h: p'); // '7' @@ -103,7 +106,23 @@ Accepts numbers too. Time(1).isValid() // true ``` -*Military time is not supported, but may be in the future (or not).* +Military (24-hour) time support + +```js +Time('13').format('hh:mm AM'); // '01:00 PM' +Time('2:30p').format('HH:mm'); // '14:30' +Time('14:30').format('h:mm AM'); // '2:30 PM' +Time('0000').format('h:mm AM'); // '12:00 AM' +Time('2400').isValid(); // false (contrary to ISO8601) +``` + +ISO8601 time component (without seconds) support + +```js +Time('12:00 am').toISOString(); // '00:00' +Time('12:00 pm').toISOString(); // '12:00' +Time('11:00 pm').toISOString(); // '23:00' +``` Test ---- diff --git a/test/format.js b/test/format.js index dc40229..a990573 100644 --- a/test/format.js +++ b/test/format.js @@ -33,10 +33,10 @@ describe('Time', function() { var t = time('03:23 pm'); var expected = '03:23'; t.format('hh:mm').should.equal(expected); - t.format('HH:mm').should.equal(expected); - t.format('Hh:Mm').should.equal(expected); - t.format('hH:MM').should.equal(expected); - t.format('H:MM').should.equal('3:23'); + // t.format('HH:mm').should.equal(expected); + t.format('hh:Mm').should.equal(expected); + t.format('hh:MM').should.equal(expected); + t.format('h:MM').should.equal('3:23'); }); it('should not care if a or p is used for period', function() { @@ -119,5 +119,40 @@ describe('Time', function() { var t = time('12:30'); t.format('h:m').should.equal('invalid format'); }); + + it('should format military time: 0', function() { + var t = time('0'); + t.format('hmm').should.equal('1200'); + t.format('hhmm').should.equal('1200'); + t.format('hh:mm AM').should.equal('12:00 AM'); + t.format('H').should.equal('0'); + t.format('HH').should.equal('00'); + t.format('HHmm').should.equal('0000'); + t.format('HH:mm').should.equal('00:00'); + }); + + it('should format military time: 13', function() { + var t = time('13'); + t.format('hmm').should.equal('100'); + t.format('hhmm').should.equal('0100'); + t.format('hh:mm AM').should.equal('01:00 PM'); + t.format('H').should.equal('13'); + t.format('HH').should.equal('13'); + t.format('HHmm').should.equal('1300'); + t.format('HH:mm').should.equal('13:00'); + t.format('HH:mm AM').should.equal('13:00'); + }); + + it('should format military time: 9:55 PM', function() { + var t = time('9:55 PM'); + t.format('hmm').should.equal('955'); + t.format('hhmm').should.equal('0955'); + t.format('hh:mm AM').should.equal('09:55 PM'); + t.format('H').should.equal('21'); + t.format('HH').should.equal('21'); + t.format('HHmm').should.equal('2155'); + t.format('HH:mm').should.equal('21:55'); + t.format('HH:mm AM').should.equal('21:55'); + }); }); }); diff --git a/test/parsing.js b/test/parsing.js index 5511377..1920894 100644 --- a/test/parsing.js +++ b/test/parsing.js @@ -103,19 +103,23 @@ describe('Time', function() { result = time(hour); result.isValid().should.be.ok; result.hours().should.equal(parseInt(hour)); + result.militaryHours().should.equal(parseInt(hour)); result.minutes().should.equal(0); } }); - it('should fail made up hours e.g. 0, 13, 50', function() { - time('0').isValid().should.not.be.ok; - time('13').isValid().should.not.be.ok; + it('should pass military time hours', function() { + time('0').isValid().should.be.ok; + time('13').isValid().should.be.ok; + time('0:20').isValid().should.be.ok; + time('13:12').isValid().should.be.ok; + }); + + it('should fail made up hours e.g. 50', function() { time('50').isValid().should.not.be.ok; }); - it('should fail made up hours e.g. 0:20, 13:12, 50:00', function() { - time('0:20').isValid().should.not.be.ok; - time('13:12').isValid().should.not.be.ok; + it('should fail made up hours e.g. 50:00', function() { time('50:00').isValid().should.not.be.ok; }); @@ -168,11 +172,33 @@ describe('Time', function() { }); it('should fail made up minutes without the colon e.g. 13, 160', function() { - time('14').isValid().should.not.be.ok; + // time('14').isValid().should.not.be.ok; time('160').isValid().should.not.be.ok; time('1299').isValid().should.not.be.ok; time('12021').isValid().should.not.be.ok; time('12218').isValid().should.not.be.ok; }); + + it('should not allow twenty four hundred hours', function() { + time('2400').isValid().should.not.be.ok; + }); + }); + + describe('#toISOString', function() { + it('should output the time component specified by ISO8601', function() { + time('12:00 am').toISOString().should.equal('00:00'); + time('1:00 am').toISOString().should.equal('01:00'); + time('11:00 am').toISOString().should.equal('11:00'); + time('12:00 pm').toISOString().should.equal('12:00'); + time('1:00 pm').toISOString().should.equal('13:00'); + time('11:00 pm').toISOString().should.equal('23:00'); + time('0').toISOString().should.equal('00:00'); + time('1').toISOString().should.equal('01:00'); + time('11').toISOString().should.equal('11:00'); + time('12').toISOString().should.equal('12:00'); + time('13').toISOString().should.equal('13:00'); + time('23').toISOString().should.equal('23:00'); + time('24').toISOString().should.equal('invalid time'); + }); }); }); diff --git a/time.js b/time.js index 80c5d76..f997606 100644 --- a/time.js +++ b/time.js @@ -5,7 +5,8 @@ , periodRegex = new RegExp('([ap](\\.?)(m\\.?)?)', 'i') , timeRegex = new RegExp('^(10|11|12|0?[1-9])(?::|\\.)?([0-5][0-9])?' + periodRegex.source + '?$', 'i') - , formatRegex = new RegExp('^(h|hh)([:|\.])?(mm)?( ?)' + , militaryTimeRegex = new RegExp('^([01]?[0-9]|2[0-3])(?::|\\.)?([0-5][0-9])?$', 'i') + , formatRegex = new RegExp('^(h|hh|H|HH)([:|\.])?(mm)?( ?)' + periodRegex.source + '?$', 'i'); // play nice with both node.js and browser @@ -15,8 +16,8 @@ /* * Time constructor works with(out) 'new' * - * @time (optional) string or number representing a time. - * e.g. 7, 1234, '7', '7:00', '12.14' + * @time (optional) string or number representing a 12-hour or 24-hour military time. + * e.g. 7, 1234, '7', '7:00', '12.14', '13', '15:30' * * If not provided, current time is used. */ @@ -26,11 +27,23 @@ var hours, minutes, period = null; if (time) { - var result = timeRegex.exec(sanitize(time)); + var sanitizedTime = sanitize(time); + // parse 12-hour time + var result = timeRegex.exec(sanitizedTime); if (result) { hours = parseInt(result[1]); minutes = result[2] ? parseInt(result[2]) : 0; period = parsePeriod(result[3]); + } else { + // parse 24-hour military time + result = militaryTimeRegex.exec(sanitizedTime); + if (result) { + hours = parseInt(result[1]); + period = hours > 11 ? PM : AM; + if (hours > 12) hours -= 12; + if (hours === 0) hours = 12; + minutes = result[2] ? parseInt(result[2]) : 0; + } } } else { // set to current time @@ -48,6 +61,27 @@ hours = parseInt(newHours); }; + this.militaryHours = function(newHours) { + if (!newHours) { + if (period === AM || !period) { + if (hours === 12 && period) + return 0; + else + return hours; + } else { + if (hours === 12) + return 12; + else { + return parseInt(hours) + 12; + } + } + } + hours = parseInt(newHours); + period = hours > 11 ? PM : AM; + if (hours > 12) hours -= 12; + if (hours === 0) hours = 12; + }; + // gets or sets minutes this.minutes = function(newMinutes) { if (!newMinutes) return minutes; @@ -91,7 +125,8 @@ }; Time.isValid = function(time) { - return timeRegex.test(sanitize(time)); + var sanitizedTime = sanitize(time); + return timeRegex.test(sanitizedTime) || militaryTimeRegex.test(sanitizedTime); }; Time.prototype.isValid = function() { @@ -125,6 +160,8 @@ * hh:mm a.m. 01:55 a.m. * h:mma 1:55a * h.mm 1.55 + * H:mm 13:55 + * HH:mm 01:00 */ Time.prototype.format = function(format) { format = format || Time.DEFAULT_TIME_FORMAT; @@ -141,6 +178,13 @@ */ Time.prototype.toString = Time.prototype.format; + /* + * Alias for `format('HH:mm')` returns ISO8601 time component (without seconds) + */ + Time.prototype.toISOString = function() { + return this.format('HH:mm'); + }; + /* * (private) Format Time in the given format. * @@ -159,7 +203,14 @@ var fPeriodM = bits[7]; // always show hour - var hours = fHour.length == 2 ? padTime(time.hours()) : time.hours(); + var hours; + // check for military format H and HH + var militaryFormat = fHour.toLowerCase() !== fHour; + + if (!militaryFormat) + hours = fHour.length == 2 ? padTime(time.hours()) : time.hours(); + else + hours = fHour.length == 2 ? padTime(time.militaryHours()) : time.militaryHours(); // show if in the format or if non-zero and middlebit is provided var minutes = (fMinutes || (fMiddlebit && time.minutes() !== 0)) ? @@ -170,7 +221,7 @@ // show period if available and requested var period = ''; - if (fPeriod && time.period()) { + if (!militaryFormat && fPeriod && time.period()) { var firstPeriod = time.period().charAt(0); if (fPeriod.charAt(0) === fPeriod.charAt(0).toUpperCase()) { firstPeriod = firstPeriod.toUpperCase();