diff --git a/.travis.yml b/.travis.yml index 24598c1..28b008f 100755 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,6 @@ env: before_install: - echo 'Canada/Pacific' | sudo tee /etc/timezone - sudo dpkg-reconfigure --frontend noninteractive tzdata - - sudo ntpdate ntp.ubuntu.com install: ant -Dtest.framework=$TESTFRAMEWORK -Dsource=remote -Dwork.dir=$HOME/work -Dbuild.dir=$TRAVIS_BUILD_DIR -Dplatform=$PLATFORM install-ci-deps script: ant -Dtest.framework=$TESTFRAMEWORK -Dsource=remote -Dwork.dir=$HOME/work -Dbuild.dir=$TRAVIS_BUILD_DIR -Dplatform=$PLATFORM test-ci diff --git a/moment.cfc b/moment.cfc old mode 100644 new mode 100755 index 137c633..d4242ac --- a/moment.cfc +++ b/moment.cfc @@ -26,7 +26,11 @@ component displayname="moment" { -- for instance initialized to someTimeValue in someTZID TZ */ public function init( time = now(), zone = getSystemTZ() ) { - this.time = (time contains '{ts') ? time : parseDateTime( arguments.time ); + if (isNumeric(time)) + this.time = epochToDate(arguments.time); + else + this.time = (time contains '{ts') ? time : parseDateTime( arguments.time ); + this.zone = zone; this.utc_conversion_offset = getTargetOffsetDiff( getSystemTZ(), zone, time ); this.utcTime = TZtoUTC( arguments.time, arguments.zone ); @@ -63,6 +67,78 @@ component displayname="moment" { return add( -1 * amount, part ); } + public function startOf( required string part ){ + part = canonicalizeDatePart(part, "startOf"); + var dest = ''; + + switch (part){ + case 'year': + dest = createDateTime(year(this.localTime),1,1,0,0,0); + break; + case 'quarter': + dest = createDateTime(year(this.localTime),(int((month(this.localTime)-1)/3)+1)*3-2,1,0,0,0); + break; + case 'month': + dest = createDateTime(year(this.localTime),month(this.localTime),1,0,0,0); + break; + case 'week': + dest = createDateTime(year(this.localTime),month(this.localTime),day(this.localTime),0,0,0); + dest = dateAdd("d", (dayOfWeek(dest)-1)*-1, dest); + break; + case 'day': + dest = createDateTime(year(this.localTime),month(this.localTime),day(this.localTime),0,0,0); + break; + case 'hour': + dest = createDateTime(year(this.localTime),month(this.localTime),day(this.localTime),hour(this.localTime),0,0); + break; + case 'minute': + dest = createDateTime(year(this.localTime),month(this.localTime),day(this.localTime),hour(this.localTime),minute(this.localTime),0); + break; + default: + throw(message="Invalid date part value, expected one of: year, quarter, month, week, day, hour, minute; or one of their acceptable aliases (see dateTimeFormat docs)"); + } + + return init( dest, this.zone ); + } + + public function endOf(required string part) { + part = canonicalizeDatePart(part, "startOf"); + + var dest = ''; + switch (part){ + case 'year': + dest = createDateTime(year(this.localTime),12,31,23,59,59); + break; + case 'quarter': + dest = createDateTime(year(this.localTime),(int((month(this.localTime)-1)/3)+1)*3,1,23,59,59); //first day of last month of quarter (e.g. 12/1) + dest = dateAdd('m', 1, dest); //first day of following month + dest = dateAdd('d', -1, dest); //last day of last month of quarter + break; + case 'month': + dest = createDateTime(year(this.localTime),month(this.localTime),1,23,59,59); //first day of month + dest = dateAdd('m', 1, dest); //first day of following month + dest = dateAdd('d', -1, dest); //last day of target month + break; + case 'week': + dest = createDateTime(year(this.localTime),month(this.localTime),day(this.localTime),23,59,59); + dest = dateAdd("d", (7-dayOfWeek(dest)), dest); + break; + case 'day': + dest = createDateTime(year(this.localTime),month(this.localTime),day(this.localTime),23,59,59); + break; + case 'hour': + dest = createDateTime(year(this.localTime),month(this.localTime),day(this.localTime),hour(this.localTime),59,59); + break; + case 'minute': + dest = createDateTime(year(this.localTime),month(this.localTime),day(this.localTime),hour(this.localTime),minute(this.localTime),59); + break; + default: + throw(message="Invalid date part value, expected one of: year, quarter, month, week, day, hour, minute; or one of their acceptable aliases (see dateTimeFormat docs)"); + } + + return init( dest, this.zone ); + } + //=========================================== //STATICS //=========================================== @@ -212,6 +288,72 @@ component displayname="moment" { return getArbitraryTimeOffset( this.time, this.zone ); } + public function year( newYear = '' ){ + if ( newYear == '' ){ + return getDatePart( 'year' ); + }else{ + return init( + time: createDateTime( newYear, month(this.time), day(this.time), hour(this.time), minute(this.time), second(this.time) ) + ,zone: this.zone + ); + } + } + + public function month( newMonth = '' ){ + if ( newMonth == '' ){ + return getDatePart( 'month' ); + }else{ + return init( + time: createDateTime( year(this.time), newMonth, day(this.time), hour(this.time), minute(this.time), second(this.time) ) + ,zone: this.zone + ); + } + } + + public function day( newDay = '' ){ + if ( newDay == '' ){ + return getDatePart( 'day' ); + }else{ + return init( + time: createDateTime( year(this.time), month(this.time), newDay, hour(this.time), minute(this.time), second(this.time) ) + ,zone: this.zone + ); + } + } + + public function hour( newHour = '' ){ + if ( newHour == '' ){ + return getDatePart( 'hour' ); + }else{ + return init( + time: createDateTime( year(this.time), month(this.time), day(this.time), newHour, minute(this.time), second(this.time) ) + ,zone: this.zone + ); + } + } + + public function minute( newMinute = '' ){ + if ( newMinute == '' ){ + return getDatePart( 'minute' ); + }else{ + return init( + time: createDateTime( year(this.time), month(this.time), day(this.time), hour(this.time), newMinute, second(this.time) ) + ,zone: this.zone + ); + } + } + + public function second( newSecond = '' ){ + if ( newSecond == '' ){ + return getDatePart( 'second' ); + }else{ + return init( + time: createDateTime( year(this.time), month(this.time), day(this.time), hour(this.time), minute(this.time), newSecond ) + ,zone: this.zone + ); + } + } + //=========================================== //QUERY //=========================================== @@ -244,7 +386,17 @@ component displayname="moment" { //=========================================== //INTERNAL HELPERS //=========================================== + + private function epochToDate( epoch ){ + var d = ""; + if (isValid("integer",arguments.epoch)) + d = createObject("java","java.util.Date").init(javacast("long",arguments.epoch*1000)); + else if (isNumeric(arguments.epoch) and val(arguments.epoch) gt 1000) + d = createObject("java","java.util.Date").init(javacast("long",arguments.epoch)); + return d; + } + private function getSystemTimeMS(){ return createObject('java', 'java.lang.System').currentTimeMillis(); } @@ -275,29 +427,35 @@ component displayname="moment" { var isDateAdd = (lcase(method) == 'dateadd'); var isDateDiff = (lcase(method) == 'datediff'); var isDateCompare = (lcase(method) == 'datecompare'); + var isStartOf = (lcase(method) == 'startof'); switch( lcase(arguments.part) ){ case 'years': case 'year': case 'y': + if (isStartOf) return 'year'; return 'yyyy'; case 'quarters': case 'quarter': case 'q': + if (isStartOf) return 'quarter'; if (!isDateCompare) return 'q'; throw(message='DateCompare doesn''t support Quarter precision'); case 'months': case 'month': case 'm': + if (isStartOf) return 'month'; return 'm'; case 'weeks': case 'week': case 'w': + if (isStartOf) return 'week'; if (!isDateCompare) return 'ww'; throw(message='DateCompare doesn''t support Week precision'); case 'days': case 'day': case 'd': + if (isStartOf) return 'day'; return 'd'; case 'weekdays': case 'weekday': @@ -307,18 +465,22 @@ component displayname="moment" { case 'hours': case 'hour': case 'h': + if (isStartOf) return 'hour'; return 'h'; case 'minutes': case 'minute': case 'n': + if (isStartOf) return 'minute'; return 'n'; case 'seconds': case 'second': case 's': + if (isStartOf) return 'second'; return 's'; case 'milliseconds': case 'millisecond': case 'ms': + if (isStartOf) return 'millisecond'; if (isDateAdd) return 'L'; if (isDateDiff) return 'L'; //custom support for ms diffing is provided interally, because adobe sucks throw(message='#method# doesn''t support Millisecond precision'); @@ -332,4 +494,8 @@ component displayname="moment" { return (targetOffset - startOffset) * 1000; } + private function getDatePart( datePart ){ + return val( format( canonicalizeDatePart( arguments.datePart ) ) ); + } + } diff --git a/readme.md b/readme.md old mode 100644 new mode 100755 index e99373d..7464de2 --- a/readme.md +++ b/readme.md @@ -51,6 +51,18 @@ Here's a list of all masks you can use with add/subtract: **\* \* Another deviation from the official dateAdd mask:** `ms` just makes more sense than the `l` (lower case L) that Adobe uses. +#### startOf / endOf + +Returns a new moment instance with the date/time shifted to the start or end of the specified date part. For example, the end of the current week*: + + endOfWeek = new moment().endOf('week'); + +Or the start of the next quarter: + + nextQuarter = new moment().startOf('quarter').add(1, 'quarter'); + +**\* Weeks are assumed to be Sun-Sat** + #### Clone Returns a new moment instance with the same datetime and time zone. @@ -163,6 +175,19 @@ After all is said and done, sometimes you just need the raw datetime object back raw = new moment( '2008-11-27 13:47' ).getDateTime(); //=> {ts '2008-11-27 13:47:00'} +#### year, month, day, hour, minute, second + +Read or write just the year portion of the moment. When updating, returns a new moment instance. + + x = new moment(); + x.year(); + //=> returns the current year (numeric) + + x.year( 2000 ); + //=> returns a new moment with the date/time rewound to the same moment in the year 2000 + +The same pattern repeats for each of the following methods: `year()`, `month()`, `day()`, `hour()`, `minute()`, `second()`. Execute it with no arguments to get the current value, or execute it with an appropriate argument value to return a new moment maching the same date and time except for the specified date-part modification. + ## Time Zones In addition to all of the great date math you can do (with moment's better syntax), moment also has baked-in support for Time Zone functionality, using the robust underlying `java.util.TimeZone` class. diff --git a/tests/testbox/tests.cfc b/tests/testbox/tests.cfc index a582a40..4fcc2a0 100644 --- a/tests/testbox/tests.cfc +++ b/tests/testbox/tests.cfc @@ -346,6 +346,68 @@ component extends="testbox.system.BaseSpec" { }); + describe("startOf()", function(){ + it("supports masks: year", function(){ + start = moment('2016-02-05 00:05:00', 'America/New_York'); + expect( start.startOf("year").format("yyyy-mm-dd HH:nn:ss") ).toBe( "2016-01-01 00:00:00" ); + }); + it("supports masks: quarter", function(){ + start = moment('2016-02-05 00:05:00', 'America/New_York'); + expect( start.startOf("quarter").format("yyyy-mm-dd HH:nn:ss") ).toBe( "2016-01-01 00:00:00" ); + }); + it("supports masks: month", function(){ + start = moment('2016-02-05 00:05:00', 'America/New_York'); + expect( start.startOf("month").format("yyyy-mm-dd HH:nn:ss") ).toBe( "2016-02-01 00:00:00" ); + }); + it("supports masks: week", function(){ + start = moment('2016-02-05 00:05:00', 'America/New_York'); + expect( start.startOf("week").format("yyyy-mm-dd HH:nn:ss") ).toBe( "2016-01-31 00:00:00" ); + }); + it("supports masks: day", function(){ + start = moment('2016-02-05 00:05:00', 'America/New_York'); + expect( start.startOf("day").format("yyyy-mm-dd HH:nn:ss") ).toBe( "2016-02-05 00:00:00" ); + }); + it("supports masks: hour", function(){ + start = moment('2016-02-05 03:05:00', 'America/New_York'); + expect( start.startOf("hour").format("yyyy-mm-dd HH:nn:ss") ).toBe( "2016-02-05 03:00:00" ); + }); + it("supports masks: minute", function(){ + start = moment('2016-02-05 00:05:17', 'America/New_York'); + expect( start.startOf("minute").format("yyyy-mm-dd HH:nn:ss") ).toBe( "2016-02-05 00:05:00" ); + }); + }); + + describe("endOf()", function(){ + it("supports masks: year", function(){ + start = moment('2016-02-05 00:05:00', 'America/New_York'); + expect( start.endOf("year").format("yyyy-mm-dd HH:nn:ss") ).toBe( "2016-12-31 23:59:59" ); + }); + it("supports masks: quarter", function(){ + start = moment('2016-02-05 00:05:00', 'America/New_York'); + expect( start.endOf("quarter").format("yyyy-mm-dd HH:nn:ss") ).toBe( "2016-03-31 23:59:59" ); + }); + it("supports masks: month", function(){ + start = moment('2016-02-05 00:05:00', 'America/New_York'); + expect( start.endOf("month").format("yyyy-mm-dd HH:nn:ss") ).toBe( "2016-02-29 23:59:59" ); + }); + it("supports masks: week", function(){ + start = moment('2016-02-05 00:05:00', 'America/New_York'); + expect( start.endOf("week").format("yyyy-mm-dd HH:nn:ss") ).toBe( "2016-02-06 23:59:59" ); + }); + it("supports masks: day", function(){ + start = moment('2016-02-05 00:05:00', 'America/New_York'); + expect( start.endOf("day").format("yyyy-mm-dd HH:nn:ss") ).toBe( "2016-02-05 23:59:59" ); + }); + it("supports masks: hour", function(){ + start = moment('2016-02-05 00:05:00', 'America/New_York'); + expect( start.endOf("hour").format("yyyy-mm-dd HH:nn:ss") ).toBe( "2016-02-05 00:59:59" ); + }); + it("supports masks: minute", function(){ + start = moment('2016-02-05 00:05:00', 'America/New_York'); + expect( start.endOf("minute").format("yyyy-mm-dd HH:nn:ss") ).toBe( "2016-02-05 00:05:59" ); + }); + }); + }); describe("STATICS", function(){ @@ -621,6 +683,7 @@ component extends="testbox.system.BaseSpec" { for(var z in testZones){ var testNoDST = moment( timeNoDST, z.zone ); var testDST = moment( timeDST, z.zone ); + debug( "Testing for: " & z.zone ); debug( testNoDST ); debug( testDST ); @@ -636,6 +699,15 @@ component extends="testbox.system.BaseSpec" { expect( testNoDST.format('dd') ).toBe( '05' ); expect( testNoDST.format('d') ).toBe( '5' ); + expect( testNoDST.format('h') ).toBe( '9' ); + expect( testNoDST.format('hh') ).toBe( '09' ); + expect( testNoDST.format('H') ).toBe( '9' ); + expect( testNoDST.format('HH') ).toBe( '09' ); + expect( testNoDST.format('n') ).toBe( '3' ); + expect( testNoDST.format('nn') ).toBe( '03' ); + expect( testNoDST.format('s') ).toBe( '7' ); + expect( testNoDST.format('ss') ).toBe( '07' ); + expect( testDST.format('yyyy') ).toBe( '2009' ); expect( testDST.format('yy') ).toBe( '09' ); expect( testDST.format('mmmm') ).toBe( 'March' ); @@ -646,7 +718,15 @@ component extends="testbox.system.BaseSpec" { expect( testDST.format('EEE') ).toBe( 'Mon' ); expect( testDST.format('dd') ).toBe( '09' ); expect( testDST.format('d') ).toBe( '9' ); - //more to come here... + + expect( testDST.format('h') ).toBe( '9' ); + expect( testDST.format('hh') ).toBe( '09' ); + expect( testDST.format('H') ).toBe( '9' ); + expect( testDST.format('HH') ).toBe( '09' ); + expect( testDST.format('n') ).toBe( '3' ); + expect( testDST.format('nn') ).toBe( '03' ); + expect( testDST.format('s') ).toBe( '7' ); + expect( testDST.format('ss') ).toBe( '07' ); //now check formattings with time zones expect( testNoDST.format('long') ).toBe( 'March 5, 2009 9:03:07 AM #z.short#' ); @@ -700,7 +780,7 @@ component extends="testbox.system.BaseSpec" { it("detects single months", function(){ var test = base.clone().add( 1, 'months' ).add( 3, 'days' ); var test2 = base.clone().add( 1, 'months' ).subtract( 3, 'days' ); - expect( test.from( base ) ).toBe( '1 month ago' ); + expect( test.from( base ) ).toBe( '4 weeks ago' ); }); it("detects multiple weeks", function(){ @@ -809,6 +889,105 @@ component extends="testbox.system.BaseSpec" { }); }); + describe("year()", function(){ + it("returns the current value when argument is empty", function(){ + var d = moment('2015-03-20 00:00:00', 'America/New_York'); + expect( d.year() ).toBe( 2015 ); + }); + it("returns an updated moment when argument is not empty", function(){ + var d = moment('2015-03-20 00:00:00', 'America/New_York'); + var test = d.year( 2014 ); + expect( test ).toBeComponent(); + expect( test.format('yyyy') ).toBe( 2014 ); + expect( test.format('mm') ).toBe( 3 ); + expect( test.format('dd') ).toBe( 20 ); + }); + }); + + describe("month()", function(){ + it("returns the current value when argument is empty", function(){ + var d = moment('2015-03-20 00:00:00', 'America/New_York'); + expect( d.month() ).toBe( 3 ); + }); + it("returns an updated moment when argument is not empty", function(){ + var d = moment('2015-03-20 00:00:00', 'America/New_York'); + var test = d.month( 5 ); + expect( test ).toBeComponent(); + expect( test.format('yyyy') ).toBe( 2015 ); + expect( test.format('mm') ).toBe( 5 ); + expect( test.format('dd') ).toBe( 20 ); + }); + }); + + describe("day()", function(){ + it("returns the current value when argument is empty", function(){ + var d = moment('2015-03-20 00:00:00', 'America/New_York'); + expect( d.day() ).toBe( 20 ); + }); + it("returns an updated moment when argument is not empty", function(){ + var d = moment('2015-03-20 00:00:00', 'America/New_York'); + var test = d.day( 7 ); + expect( test ).toBeComponent(); + expect( test.format('yyyy') ).toBe( 2015 ); + expect( test.format('mm') ).toBe( 03 ); + expect( test.format('dd') ).toBe( 7 ); + }); + }); + + describe("hour()", function(){ + it("returns the current value when argument is empty", function(){ + var d = moment('2015-03-20 09:00:00', 'America/New_York'); + expect( d.hour() ).toBe( 9 ); + }); + it("returns an updated moment when argument is not empty", function(){ + var d = moment('2015-03-20 09:10:11', 'America/New_York'); + var test = d.hour( 13 ); + expect( test ).toBeComponent(); + expect( test.format('yyyy') ).toBe( 2015 ); + expect( test.format('mm') ).toBe( 03 ); + expect( test.format('dd') ).toBe( 20 ); + expect( test.format('HH') ).toBe( 13 ); + expect( test.format('nn') ).toBe( 10 ); + expect( test.format('ss') ).toBe( 11 ); + }); + }); + + describe("minute()", function(){ + it("returns the current value when argument is empty", function(){ + var d = moment('2015-03-20 09:03:00', 'America/New_York'); + expect( d.minute() ).toBe( 3 ); + }); + it("returns an updated moment when argument is not empty", function(){ + var d = moment('2015-03-20 09:10:11', 'America/New_York'); + var test = d.minute( 44 ); + expect( test ).toBeComponent(); + expect( test.format('yyyy') ).toBe( 2015 ); + expect( test.format('mm') ).toBe( 03 ); + expect( test.format('dd') ).toBe( 20 ); + expect( test.format('HH') ).toBe( 09 ); + expect( test.format('nn') ).toBe( 44 ); + expect( test.format('ss') ).toBe( 11 ); + }); + }); + + describe("second()", function(){ + it("returns the current value when argument is empty", function(){ + var d = moment('2015-03-20 09:00:59', 'America/New_York'); + expect( d.second() ).toBe( 59 ); + }); + it("returns an updated moment when argument is not empty", function(){ + var d = moment('2015-03-20 09:10:11', 'America/New_York'); + var test = d.second( 22 ); + expect( test ).toBeComponent(); + expect( test.format('yyyy') ).toBe( 2015 ); + expect( test.format('mm') ).toBe( 03 ); + expect( test.format('dd') ).toBe( 20 ); + expect( test.format('HH') ).toBe( 9 ); + expect( test.format('nn') ).toBe( 10 ); + expect( test.format('ss') ).toBe( 22 ); + }); + }); + }); describe("QUERY", function(){