Skip to content
Colin T.A. Gray edited this page Jul 10, 2013 · 2 revisions

NSDate objects are converted to Time objects automatically by rubymotion. That's the good news. The bad news? That still doesn't help a lot with some of the everyday date & time crap we have to deal with. (I hate dates, especially recurring events)

NSDate Package

  1. Adds the following methods to get date and time components: date_array, time_array, datetime_array. These methods return arrays. Comparing dates, times, or both become simple date1.date_array == date2.date_array.
  2. While I would love to support date + 1.month and have that support "smart" calendar math (e.g. "2/13/2013" + 1.month => "3/13/2013"), I can't fudge with the return value of 1.month (=> Fixnum), and I won't make the terrible assumption that "30 days of seconds is about one month". So instead, a new method that accepts date components as options is introduced: date.delta(months:1)
  3. Checking whether two dates are the same, ignoring the time components, is often required. start_of_day and end_of_day methods help you here. They are akin to floor and ceil if you consider the time to be the "floating" component, and the date to be the nearest "integer".
  4. Formatting is made easier with NSDate#string_with_style(NSDateStyleConstant or Symbol for date, time) and NSDate#string_with_format(format_string). See http://www.unicode.org/reports/tr35/tr35-25.html#Date_Format_Patterns for the formatters, they take getting used to, coming from strftime, but they are much more powerful and locale-aware.
  5. Miscellaneous other helpers and class additions. I'll go over these first.

Helpers

Fixnum additions
# the time of this writing is 2013-01-03 11:42:24 -0700
(main)> 5.days.ago
=> 2012-12-29 11:42:24 -0700
(main)> 5.days.before(NSDate.new)
=> 2012-12-29 11:42:24 -0700
(main)> 5.days.hence
=> 2013-01-08 11:42:24 -0700
(main)> 5.days.after(NSDate.new)
=> 2013-01-08 11:42:24 -0700
# don't confuse 'after' and 'later'
# after => NSDate
# later => NSTimer
NSString additions
"Asia/Tokyo".nstimezone
"America/Denver".nstimezone
"UTC".nstimezone
"UTC+9".nstimezone
"GMT+9".nstimezone
"+0900".nstimezone
NSDate additions
(main)> now = NSDate.new  # Time.new is the same thing
=> 2012-09-13 09:19:06 -0600

# NSDate##from_components
(main)> feb_1_2013 = NSDate.from_components(year: 2013, month: 2, day:1)
=> 2013-02-01 00:00:00 -0700
(main)> feb_1_2013_sometime_later = NSDate.from_components(year: 2013, month: 2, day:1, hour:13, minute: 59, second:30)
=> 2013-02-01 13:59:30 -0700
(main)> feb_1_2012 = NSDate.from_components(year: 2012, month: 2, day:1)
=> 2012-02-01 00:00:00 -0700


(main)> feb_1_2013.timezone.name
=> "America/Denver"
(main)> feb_1_2013.era
=> 1  # no, I don't know what this is :-/
(main)> feb_1_2013.today?
=> false  # actually, at the time I am WRITING this, it IS true, but by the time
          # you read it, not so much ;-)
(main)> NSDate.new.today?
=> true
(main)> feb_1_2013.same_day?(NSDate.new)
=> false
(main)> feb_1_2013.same_day?(feb_1_2013_sometime_later)
=> true  # compares just the date, ignores the time
(main)> feb_1_2013.utc_offset
=> -25200
(main)> feb_1_2013.leap_year?
=> false
(main)> NSDate.from_components(year: 2012).leap_year?
=> true
(main)> feb_1_2013.start_of_day
=> 2013-02-01 00:00:00 -0700
(main)> feb_1_2013.end_of_day
# NOTE! end_of_day is the NEXT DAY.  this is not an accident, it makes comparisons cleaner.  deal with it.
=> 2013-02-02 00:00:00 -0700
(main)> feb_1_2013.start_of_week  # in the USA, start of week is Sunday
=> 2013-01-27 00:00:00 -0700
=> 2013-01-28 00:00:00 -0700  # in most other countries you will get Monday
(main)> feb_1_2013.start_of_week(:monday)  # or you can specify it!
=> 2013-01-28 00:00:00 -0700
(main)> feb_1_2013.end_of_week  # Just like end_of_day, end_of_week returns midnight of the *next day*
=> 2013-02-03 00:00:00 -0700
(main)> feb_1_2013.end_of_week(:monday)
=> 2013-02-04 00:00:00 -0700
(main)> feb_1_2013.days_in_month
=> 28
(main)> feb_1_2013.days_in_year
=> 365
(main)> feb_1_2012.days_in_month
=> 29
(main)> feb_1_2012.days_in_year
=> 366

(main)> now.date_array
=> [2012, 9, 13]
(main)> now.time_array
=> [9, 19, 6]
(main)> now.datetime_array
=> [2012, 9, 13, 9, 19, 6]

Use NSDate#string_with_style to generate date and/or time strings.

(main)> now.string_with_style
=> "January 29, 2013"
(main)> now.string_with_style(NSDateFormatterShortStyle)
=> "1/29/13"
(main)> now.string_with_style(:short)
=> "1/29/13"
(main)> now.string_with_style(NSDateFormatterMediumStyle, NSDateFormatterShortStyle)
=> "Jan 29, 2013, 9:19 AM"
(main)> now.string_with_style(:short, :medium)
=> "1/29/13, 9:19:06 AM"
(main)> now.string_with_style(:none, :long)
=> "9:19:06 AM GMT+01:00"

NSDate#string_with_format accepts a date template string. See http://www.unicode.org/reports/tr35/tr35-19.html#Date_Field_Symbol_Table for the available symbols.

(main)> now.string_with_format('MMMM')
=> "January"
(main)> now.string_with_format('MMMM dd, YYYY')
=> "January 29, 2013"
# there are some symbols that work, too. :iso8601, :ymd, :hms
(main)> now.string_with_format(:iso8601)
=> "2013-01-29 09:19:06.410"

It is easy to add seconds to the date using the time-related methods added to Numeric, though the NSDate#delta method is MUCH more capable.

(main)> now + 5
=> 2012-09-13 09:19:11 -0600
(main)> now - 5
=> 2012-09-13 09:19:01 -0600
(main)> now + 5.minutes
=> 2012-09-13 09:24:06 -0600
(main)> now + 5.days
=> 2012-09-18 09:19:06 -0600

Time zone objects are available, but the Time#utc_offset method is a little more useful. It returns the offset in seconds, so divide by 1.0.hour to get the offset in hours. utc_offset is built into Time, not added by SugarCube, but it is added to the NSDate class in case you get one of those instead.

(main)> now.timezone
=> #<__NSTimeZone:0x9384c70>
(main)> now.timezone.name
=> "America/Denver"
(main)> now.utc_offset
=> -21600
(main)> now.utc_offset / 1.hour
=> -6

NSDate#delta method

The delta method is smart. See the tests! It will do its best to compensate for daylight savings, leap years, different numbers of days in the month, and so on.

(main)> feb_28_2012 = NSDate.from_components(year:2012, month: 2, day: 28)
=> 2012-02-28 17:00:00 -0700

# add an hour or two
(main)> feb_28_2012.delta(hours:1)
=> 2012-02-28 18:00:00 -0700
(main)> feb_28_2012.delta(hours:2)
=> 2012-02-28 19:00:00 -0700

# add some days
(main)> feb_28_2012.delta(days:1)
=> 2012-02-29 17:00:00 -0700
(main)> feb_28_2012.delta(days:2)
=> 2012-03-01 17:00:00 -0700

# how about a month?
(main)> feb_28_2012.delta(months:1)
=> 2012-03-28 17:00:00 -0600  # look, the time didn't change, event though there was a DST change in this period!

# cool, but if you want a more literal "24 hours", specify a time unit
(main)> feb_28_2012.delta(months:1, hours:0)
=> 2012-03-28 18:00:00 -0600  # disable the DST fix by specifying hours, minutes, or seconds (a "precise" delta)

# in one year, it will still be Feb 28th
(main)> feb_28_2012.delta(years:1)
=> 2013-02-28 17:00:00 -0700

# and we already know what adding a day looks like
(main)> feb_28_2012.delta(days:1)
=> 2012-02-29 17:00:00 -0700

# a year and a day is tricky, because do we add a day, then a year?  or add a
# year and then a day?  well, i'll tell you, **I** add a day and then a year,
# which is feb 29th, which is no good, and the algorithm rolls back days to the
# last day of the month, so we get the 28th.
(main)> feb_28_2012.delta(days:1, years:1)
=> 2013-02-28 17:00:00 -0700

# adding 2 days puts us into March, which then "looks right", but it's both
# right AND wrong, depending on how you look at it.  Another example is below,
# where we add a month to January 30th.  Really, though, think of this: how
# often do you need to add a year AND a day!?  Adding a year is more common, and
# this is showing that adding a year to Feb 29th will give you Feb 28th, which I
# think is better than March 1st.
(main)> feb_28_2012.delta(days:2, years:1)
=> 2013-03-01 17:00:00 -0700

# Crazier: add a day (Feb 29th), then a month (March 29th), THEN a year.
(main)> feb_28_2012.delta(days:1, years:1, months:1)
=> 2013-03-29 17:00:00 -0600

# k, for the next examples, we need a new date, and this is a non-leap year.
(main)> jan_29_2013 = feb_28_2012.delta(days:1, months:11)
=> 2013-01-29 17:00:00 -0700

# what is 1/29/2013 plus two months?  easy!  march 29, 2013
(main)> jan_29_2013.delta(months:2)
=> 2013-03-29 17:00:00 -0600

# Yeah, smart guy?  Well then what is 1/29/2013 plus ONE month. It's Feb 28th.
# When someone says "see you in a month!" they mean "next month", not "in the
# early part of two months in the future", which is where the math will take you
# if you don't add a "day of month" correction.
(main)> jan_29_2013.delta(months:1)
=> 2013-02-28 17:00:00 -0700
# but last year was a leap year, so we should get Feb 29th, 2012:
(main)> jan_29_2013.delta(months:1, years: -1)
=> 2012-02-29 17:00:00 -0700  # success!

# do other deltas work in reverse?  fuuuuuu...
(main)> jan_29_2013.delta(months:-11)
=> 2012-02-29 17:00:00 -0700
# ...ck yeah!  :-)

# daylight savings!?  GEEZ dates are annoying
(main)> mar_10_2013 = NSDate.from_components

# unfortunately you will, in the edge cases, end up with stuff like this:
(main)> feb_28_2012 == feb_28_2012.delta(days:1, months:12).delta(days: -1, months:-12)
=> 2012-02-29 00:00:00 -0700
Clone this wiki locally