-
Notifications
You must be signed in to change notification settings - Fork 66
NSDate
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)
- 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 simpledate1.date_array == date2.date_array
. - 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 of1.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)
- Checking whether two dates are the same, ignoring the time components, is often
required.
start_of_day
andend_of_day
methods help you here. They are akin tofloor
andceil
if you consider the time to be the "floating" component, and the date to be the nearest "integer". - Formatting is made easier with
NSDate#string_with_style(NSDateStyleConstant or Symbol for date, time)
andNSDate#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 fromstrftime
, but they are much more powerful and locale-aware. - Miscellaneous other helpers and class additions. I'll go over these first.
# 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
"Asia/Tokyo".nstimezone
"America/Denver".nstimezone
"UTC".nstimezone
"UTC+9".nstimezone
"GMT+9".nstimezone
"+0900".nstimezone
(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
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