Date math for lazy people

I’ve always hated doing simple date manipulation and having to create wacky variables like “millisecondsPerDay” where you multiply milliseconds by seconds by hours by whatever else. So here’s a useful (and largely untested) function, dateAdd(), which is similar to the function that ColdFusion-ers have enjoyed for years.

Full code (and instructions) after the jump.

In addition to my code being untested, I should point out that the datepart strings are different than those in ColdFusion. Where ColdFusion uses formats such as: “yyyy”, “q”, “m”, “y”, “d”, “w”, etc. I’ve cut corners and used the property names from the Date class (“fullYear”, “month”, “date”, “hours”, “minutes”, “seconds”, “milliseconds”) instead. This let me use:

returnDate[datepart] += number;

Which is basically a shorthand for something like:

returnDate["months"] += 3;

If you wanted to use the ColdFusion type notation it should be easy enough to extend the dateAdd() function and add another simple switch statement which converts the “yyyy” to “fullYear”, and so on.

Using the dateAdd() function is [hopefully] pretty straight forward.

  • All three parameters are optional. If no parameters are passed, the current date is returned (similar to the Date class constructor).
  • If only the first parameter (datepart) is passed, the current date is returned.
  • If the first two parameters (datepart and number) are passed, the function adds the specified number of date parts to the current date. For example, if you called dateAdd("month", 3) the function would return the current date plus 3 months. If you called dateAdd("month", -3) the function would return the current date minus 3 months.
  • If the first three parameters (datepart, number, and date) are passed, the function adds the specified number of date parts to the specified date. For example, if you called dateAdd("day", 15, calendarStartDate) the function would return the value of the calendarStartDate variable (which must be a Date object) plus 15 days.
<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2007/08/24/date-math-for-lazy-people/ -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
        layout="vertical"
        verticalAlign="middle"
        backgroundColor="white"
        creationComplete="init()">
 
    <mx:Script>
        <![CDATA[
            import mx.controls.dataGridClasses.DataGridColumn;
            private function init():void {
                var zeroDate:Date = new Date(0);
 
                /* fullYear */
                arrColl.addItem({label:"+2 years", data:dateAdd("fullYear", 2, zeroDate)});
                arrColl.addItem({label:"-2 years", data:dateAdd("fullYear", -2, zeroDate)});
                /* month */
                arrColl.addItem({label:"+11 months", data:dateAdd("month", 11)});
                arrColl.addItem({label:"-11 months", data:dateAdd("month", -11)});
                /* date */
                arrColl.addItem({label:"+4 date", data:dateAdd("date", 4)});
                arrColl.addItem({label:"-4 date", data:dateAdd("date", -4)});
                /* hours */
                arrColl.addItem({label:"+6 hours", data:dateAdd("hours", 6)});
                arrColl.addItem({label:"-6 hours", data:dateAdd("hours", -6)});
                /* minutes */
                arrColl.addItem({label:"+45 minutes", data:dateAdd("minutes", 45)});
                arrColl.addItem({label:"-45 minutes", data:dateAdd("minutes", -45)});
                /* seconds */
                arrColl.addItem({label:"+900 seconds", data:dateAdd("seconds", 900)});
                arrColl.addItem({label:"-900 seconds", data:dateAdd("seconds", -900)});
                /* milliseconds */
                arrColl.addItem({label:"+720000 milliseconds", data:dateAdd("milliseconds", 720000)});
                arrColl.addItem({label:"-720000 milliseconds", data:dateAdd("milliseconds", -720000)});
            }
 
            private function dateAdd(datepart:String = "", number:Number = 0, date:Date = null):Date {
                if (date == null) {
                    /* Default to current date. */
                    date = new Date();
                }
 
                var returnDate:Date = new Date(date.time);;
 
                switch (datepart.toLowerCase()) {
                    case "fullyear":
                    case "month":
                    case "date":
                    case "hours":
                    case "minutes":
                    case "seconds":
                    case "milliseconds":
                        returnDate[datepart] += number;
                        break;
                    default:
                        /* Unknown date part, do nothing. */
                        break;
                }
                return returnDate;
            }
 
            private function data_labelFunc(item:Object, column:DataGridColumn):String {
                return dateFormatter.format(item[column.dataField]);
            }
        ]]>
    </mx:Script>
 
    <mx:ArrayCollection id="arrColl" />
 
    <mx:DateFormatter id="dateFormatter"
            formatString="YYYY/MM/DD HH:NN:SS" />
 
    <mx:Label text="Now: {dateAdd()}" />
    <mx:DataGrid id="dataGrid"
            dataProvider="{arrColl}"
            sortableColumns="false"
            width="400">
        <mx:columns>
            <mx:DataGridColumn dataField="label"
                    headerText="Label"
                    width="150" />
            <mx:DataGridColumn dataField="data"
                    headerText="YYYY/MM/DD HH:NN:SS"
                    labelFunction="data_labelFunc" />
        </mx:columns>
    </mx:DataGrid>
 
</mx:Application>

View source is enabled in the following example.

As a bonus tip… I believe you could do this with the Date constructor in ActionScript 2.0 as well, but I always find this handy. The following snippet creates a new Date object for January 45th, 2007. ActionScript cleverly adjusts that for you and returns the date of February 14th, 2007.

const JANUARY:uint = 0;
// ...
const DECEMBER:uint = 11;
 
var d:Date = new Date(2007, JANUARY, 45);
trace(d.toDateString()); // Wed Feb 14 2007

Although DO note that the Date.toDateString() method (as well as uint data type and const keyword) are specific to ActionScript 3.0.

Happy Flexing!

34 thoughts on “Date math for lazy people

  1. I think this should work for the various date aliases, bringing my dateAdd() function a bit closer in line with ColdFusion’s (with the exception of support for “q” (quarter) and “ww” (week)):

    private function dateAdd(datepart:String = "", number:Number = 0, date:Date = null):Date {
        if (date == null) {
            /* Default to current date. */
            date = new Date();
        }
    
        var returnDate:Date = new Date(date.time);
    
        switch(datepart.toLocaleLowerCase()) {
            case "yyyy":
            case "year":
                datepart = "fullYear";
                break;
            case "m":
                datepart = "month";
                break;
            case "y":
            case "d":
            case "w":
            case "day":
                datepart = "date";
                break;
            case "h":
                datepart = "hours";
                break;
            case "n":
                datepart = "minutes";
                break;
            case "s":
                datepart = "seconds";
                break;
            case "l":
                datepart = "milliseconds";
                break;
        }
    
        switch (datepart.toLowerCase()) {
            case "fullyear":
            case "month":
            case "date":
            case "hours":
            case "minutes":
            case "seconds":
            case "milliseconds":
                returnDate[datepart]  = number;
                break;
            default:
                /* Unknown date part, do nothing. */
                break;
        }
        return returnDate;
    }
    
  2. “fullyear” does not work. It’s actualy “fullYear” with the capital Y.
    To fix this remove the “.toLowerCase()” and pass in correct case. change the
    line-> case “fullyear”:
    to-> case “fullYear”:

    Thanks for the example. I find this site more helpful than the Adobe site for finding the exact tweek you need.

    Keep up the great work.

  3. A optimize version of the code.

    Note: I do not use a ColdFusion Server. Modify it has your require for that.

    public static function dateAdd(datepart:String = "", number:Number = 0, date:Date = null):Date {
        date = (date == null) ? new Date() : new Date(date.time);
     
        switch(datepart.toLocaleLowerCase()) {
            case "y":
            case "fullyear":
            case "yyyy":
            case "year":
                datepart = "fullYear";
                break;
     
            case "m":
            case "month":
                datepart = "month";
                break;
     
            case "d":
            case "day":
            case "date":
                datepart = "date";
                break;
     
            case "h":
            case "hours":
                datepart = "hours";
                break;
     
            case "minutes":
            case "n":
                datepart = "minutes";
                break;
     
            case "seconds":
            case "s":
                datepart = "seconds";
                break;
     
            case "milliseconds":
            case "l":
                datepart = "milliseconds";
                break;
            default:
                datepart = null;
        }
     
        if(datepart != null)
            date[datepart]  += number;
        return date;
    }
  4. To add a week support, you can modify your script, and add this lines:

    switch(datepart.toLocaleLowerCase())
    {
        ...
     
        case "week":
            datepart = "date";
            number = number * 7;
            break;
     
        ...
    }
  5. I notice that it gets a little goofy calculating month adds when the current month is longer than the target month. For instance:
    dateAdd(“m”,1,’1/31/2009′) ends up as 3/2/2009

    Any thoughts on a workaround there? I ended up writing my own like this:

    date = new Date(date["fullYear"]+Math.floor((date["month"]+number)/12),(date["month"]+number)%12,date["date"]);

    However, I am a Flex noob and figured that you might have a more glamorous approach. :)

  6. crap… I just realized I posted an earlier iteration of that solution. On the one I ended up going with I determined whether or not the max days in month of the target month was less than date["date"], and if so, I would use that value instead of date["date"] like you see above.

  7. You are awesome…I always find everything here i want in flex….please dont stop this as you are helping many students like me…

  8.     public static function math(date:Date, number:Number=0, method:String='Day'):Date {
          if(date == null) {
            PopupUtil.error('DateUtil.dateMath did not receive a date');
            return date;
          }
          var timeAdjustment:Number = 0;
          switch(method.toLowerCase()) {
            case 'y':
            case 'year':
            case 'years':
                timeAdjustment = MS_IN_YEAR*number;
                break;
            case 'mon':
            case 'month':
            case 'months':
                timeAdjustment = MS_IN_DAY*number*30;
                break;
            case 'w':
            case 'week':
            case 'weeks':
                timeAdjustment = MS_IN_DAY*number*7;
                break;
            case 'd':
            case 'day':
            case 'days':
                timeAdjustment = MS_IN_DAY*number;
                break;
            case 'h':
            case 'hour':
            case 'hours':
                timeAdjustment = MS_IN_HOUR*number;
                break;
            case 'n':
            case 'm':
            case 'min':
            case 'mins':
            case 'minutes':
                timeAdjustment = MS_IN_MIN*number;
                break;
            case 's':
            case 'sec':
            case 'seconds':
                timeAdjustment = MS_IN_SEC*number;
                break;
            case 'ms':
            case 'milliseconds':
                method = 'milliseconds';
                timeAdjustment = number;
                break;
            default:
                PopupUtil.error('DateUtil.dateMath did not receive a method');
                return date;
          }
          return new Date(date.getTime() + timeAdjustment);
        }
  9. I created the above from the information on this blog. In response the month issues posted above, you really cant add or subtract 1 month.. since months have different lengths.. and a date can be at any spot within that month.

    Since this was date math for lazy people, I figured the best you can do is add or subtract 30 days. Just make note that when your coding… subtracting 1 month from June 21 wont put you on May 21.

    I dont even use the month math… like, if i only want items that were created within the last 3 months. I just pull the last 90 days.. and, of course, i state them as “items created in last 90 days”, NOT “in the last 3 months”

  10. Oh.. you may want this too.

        
        public static const MS_IN_SEC:int = 1000;
        public static const MS_IN_MIN:int = 1000 * 60;
        public static const MS_IN_HOUR:int = 1000 * 60 * 60;
        public static const MS_IN_DAY:int = 1000 * 60 * 60 * 24;
        public static const MS_IN_YEAR:Number = 1000 * 60 * 60 * 24 * 365;
    
  11. hi Peter,

    I am trying to get the week no of a date.

    For example,

    Date: 3rd September 2009
    Week: 36

    Date: 7 September 2009
    Week: 37
    .
    .
    .
    Date: 29 December 2009
    Week: 53

    do u have an idea where i can get the week no from a date?

    Thanks in advance for your help

    1. @Didi,

      I believe a few comments above deal with getting week numbers, but you could try something like this (relatively untested) code:

      <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
       
          <mx:Script>
              <![CDATA[
                  private const MILLISECONDS_IN_WEEK:uint = 1000 /* milliseconds */ * 
                                                              60 /* seconds */ * 
                                                              60 /* minutes */ * 
                                                              24 /* hours */ * 
                                                               7 /* days */;
       
                  private function calculateWeekNumber(dat:Date):int {
                      var jan1:Date = new Date(dat.fullYear, 0, 1);
                      var diff:Number = dat.time - jan1.time;
                      var diffInWeeks:uint = diff / MILLISECONDS_IN_WEEK;
                      return diffInWeeks;
                  }
              ]]>
          </mx:Script>
       
          <mx:DateField id="datField" />
       
          <mx:Label id="lbl" text="Week: {calculateWeekNumber(datField.selectedDate)}" />
       
      </mx:Application>

      Peter

  12. Have you seen the adobe version of this in the fr.runtime.DateTimeFunc class?

    http://livedocs.adobe.com/flex/gumbo/langref/index.html?fr/runtime/lib/DateTimeFunc.html

    I’m not sure where to get this from, it doesn’t appear to be in the flex4 svn source or anywhere else reasonable… apparently, based on the package name, it has something to do with ‘fiber’ which I think is something to do with live cycle 3… anyway, if someone knows anything more about this and could direct us to a download of this package, that’d be rad.

    Regards,

    Tim

  13. I’ve written a pretty full featured FlexDateUtils library that handles dateAdd (plus a slew of other date functions) with constants to help reference the various dateparts. It’s posted in the google code base at http://code.google.com/p/flexdateutils/ or Ray Camden’s riaforge, flexdateutils.riaforge.org Hopefully it’ll be useful to some of the posters here.

  14. can someone please help me . i need to know how to calculate the number of days between two different dates?
    Cheers,
    Maria

    1. @Maria,

      Try this… You can use the time property of each Date object to convert the date to milliseconds, then subtract the dates and divide by the number of milliseconds in a single day (1000 X 60 X 60 X 24):

      <?xml version="1.0"?>
      <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init();">
       
          <mx:Script>
              <![CDATA[
                  public const date1:Date = new Date(2010, 6, 4); /* Jul 4, 2010 */
                  public const date2:Date = new Date(2010, 11, 31); /* Dec 31, 2010 */
       
                  public const MS_IN_DAYS:Number = 1000 /* ms in sec */ * 60 /*secs in mins */ * 60 /* mins in hr */ * 24 /* hours in day */; 
       
                  public function init():void {
                      var diffInMS:Number = date2.time - date1.time;
                      result.text = (diffInMS / MS_IN_DAYS).toFixed(1);
                  }
              ]]>
          </mx:Script>
       
          <mx:Label text="Days between {date1.toDateString()} and {date2.toDateString()}:" />
          <mx:Label id="result" />
       
      </mx:Application>

      Peter

      1. Hi Peter i am starting with adobe live cycle i dont know how to included this code in there and how to call the variables, can you give me a hand. Thanks.

  15. You made my day, thank you very much…. the only problem fullyear its witha a capital Y fullYear…. think somebody comment on that

    excelent

  16. Hello All,

    I have two DatesFields. One is Start Date and other is End Date. The start date is always the current date and the end date should be 7 days ago from the current date. Can someone help me in this respect?

    Thanks,
    Nirmal Kumar Bhogadi

    1. @Nirmal Kumar Bhogadi,

      <?xml version="1.0" encoding="utf-8"?>
      <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                      initialize="init();">
       
          <mx:Script>
              <![CDATA[
                  protected function init():void {
                      var now:Date = new Date();
                      var lastWeek:Date = new Date(now.time);
                      lastWeek.date -= 7;
                      df1.selectedDate = now;
                      df2.selectedDate = lastWeek;
                  }
              ]]>
          </mx:Script>
       
          <mx:DateField id="df1" />
          <mx:DateField id="df2" />
       
      </mx:Application>

      Peter

  17. Cool add to for my utility bag. Thanks Peter!
    On an aside, I’m trying to get start-of-week date with:

    startDate = new Date();
    startDate.date -= (startDate.day-1);

    endDate = new Date();
    endDate.date += (7-startDate.day);

    but for example, today 11/10, I’m getting 11/8 – 11/16 and I would’ve thought it’ be 11/7 – 11/13
    Anyone wanna set me straight?

    Thx,
    Greg

  18. Man, I hate answering my own questions, well, in public anyway.
    I took a long-hand approach which works for finding the date of the first day of the week :

    startDate = new Date();
    var dow:int = startDate.day;
    startDate = new Date(startDate.fullYear, startDate.month, startDate.date – dow);
    endDate = new Date(startDate.fullYear, startDate.month, startDate.date + 6);

    Peter, love your blog. More like a “Help me! Adobe has totally bent the language up, and I don’t know where to…Oh, yeah. That Peter guy seems to always have the answer.”

    1. @greg,

      Thanks for the follow up. I hadn’t had a chance to look at it yet.

      Re: “Peter, love your blog. More like a “Help me! Adobe has totally bent the language up, and I don’t know where to…Oh, yeah. That Peter guy seems to always have the answer.””,

      That actually made me laugh out loud (and not because I work at Adobe).

      Peter

  19. Hi,

    i have a problem face from last day.

    The problem is that i want to run a countdown Timer. The timer is start from two dates
    1. current date (as New Date()).
    2. the immediate Monday (as new Date(“Mon Jan 17 2011″))

    The problem is with 2nd date.

    How i dynamically find the next Monday date ?.

    Thanks and welcome for Help.

Comments are closed.