Class: NSDate

Inherits:
Object show all
Defined in:
lib/cocoa/sugarcube-nsdate/nsdate.rb,
lib/cocoa/sugarcube-nsdate/nsdate_delta.rb

Constant Summary collapse

SugarCubeFormats =

these formatters are used in ‘string_with_format`. Symbols are converted to a string using string_with_format’s templating, and strings are concatenated as-is

{
  iso8601: [:yyyy, '-', :MM, '-', :dd, ' ', :'HH:mm:ss.SSS'],
  universal: [:yyyy, '-', :MM, '-', :dd, 'T', :'HH:mm:ss'],
  ymd: [:yyyy, '-', :MM, '-', :dd],
  hms: [:'HH:mm:ss.SSS'],
}

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.from_components(components) ⇒ Object



12
13
14
15
16
17
18
19
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 12

def self.from_components(components)
  date_components = NSDateComponents.new
  components.each do |property,value|
    date_components.send("#{property}=", value)
  end
  calendar = NSCalendar.alloc.initWithCalendarIdentifier(NSGregorianCalendar)
  return calendar.dateFromComponents(date_components)
end

.nowObject

Time.now is defined, but not NSDate.now.



22
23
24
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 22

def self.now
  NSDate.new
end

.todayObject



26
27
28
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 26

def self.today
  NSDate.new.start_of_day
end

.tomorrowObject



30
31
32
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 30

def self.tomorrow
  NSDate.new.delta(days: 1).start_of_day
end

.yesterdayObject



34
35
36
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 34

def self.yesterday
  NSDate.new.delta(days: -1).start_of_day
end

Instance Method Details

#_string_with_nsdate_format(format, timezone, locale) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 100

def _string_with_nsdate_format(format, timezone, locale)
  locale_name = locale.localeIdentifier
  @@sugarcube_date_formatters ||= {}
  @@sugarcube_date_formatters[locale_name] ||= {}
  @@sugarcube_date_formatters[locale_name][format] ||= begin
    format_template = NSDateFormatter.dateFormatFromTemplate(format, options: 0,
                                                      locale: locale)
    date_formatter = NSDateFormatter.new
    date_formatter.setDateFormat(format_template)
    date_formatter
  end

  date_formatter = @@sugarcube_date_formatters[locale_name][format]
  date_formatter.setTimeZone(timezone)
  return date_formatter.stringFromDate(self)
end

#_string_with_sugarcube_format(format, timezone) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 84

def _string_with_sugarcube_format(format, timezone)
  formatters = SugarCubeFormats[format]
  raise "No format found for #{format.inspect}" unless formatters
  locale = NSLocale.localeWithLocaleIdentifier('en_US')
  retval = ''
  formatters.each do |formatter|
    case formatter
    when Symbol
      retval << string_with_format(formatter.to_s, locale: locale, timezone: timezone)
    when String
      retval << formatter
    end
  end
  return retval
end

#_string_with_unicode_format(format, timezone) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 117

def _string_with_unicode_format(format, timezone)
  @@sugarcube_unicode_formatters ||= {}
  @@sugarcube_unicode_formatters[format] ||= begin
    date_formatter = NSDateFormatter.new
    date_formatter.setDateFormat(format)
    date_formatter
  end

  date_formatter = @@sugarcube_unicode_formatters[format]
  date_formatter.setTimeZone(timezone)
  return date_formatter.stringFromDate(self)
end

#date_arrayObject

(main)> t = Time.new

=> 2012-09-27 11:29:12 +0900
(main)> t.time_array
=> [2012, 9, 27]


207
208
209
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 207

def date_array
  return [self.year, self.month, self.day]
end

#datetime_arrayObject

(main)> t = Time.new

=> 2012-09-27 11:29:12 +0900
(main)> t.time_array
=> [2012, 9, 12, 11, 29, 12]


223
224
225
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 223

def datetime_array
  return [self.year, self.month, self.day, self.hour, self.min, self.sec]
end

#days_in_monthObject



289
290
291
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 289

def days_in_month
  NSCalendar.currentCalendar.rangeOfUnit(NSDayCalendarUnit, inUnit:NSMonthCalendarUnit, forDate:self).length
end

#days_in_yearObject



293
294
295
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 293

def days_in_year
  leap_year? ? 366 : 365
end

#delta(_components) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/cocoa/sugarcube-nsdate/nsdate_delta.rb', line 3

def delta(_components)
  components = {}.update(_components)
  is_very_specific = components.has_key?(:seconds)
  is_very_specific ||= components.has_key?(:minutes)
  is_very_specific ||= components.has_key?(:hours)

  y = components.delete(:years) || 0
  mo = components.delete(:months) || 0
  d = components.delete(:days) || 0
  h = components.delete(:hours) || 0
  mi = components.delete(:minutes) || 0
  s = components.delete(:seconds) || 0
  w = components.delete(:weeks) || 0
  raise "Unknown arguments #{components.keys}" unless components.empty?

  is_dst = self.dst?

  delta = s
  # todo: leap second adjustment?  can leap seconds be detected?
  delta += mi * 60
  delta += h * 3600

  return_date = self + delta

  # using days_in_month, this is pretty easy.  12 mos per year IS a constant,
  # and then we just keep adding the number of days in the month (or last month
  # if we're going backwards).  The curve ball is that when we are in day
  # 29,30,31, we might jump forward a month and "fall off" to the next month.
  # In this case, we add a correction.  We will always move forwards or
  # backwards until the return_date.day is correct.
  mo += y * 12
  if mo != 0
    if return_date.day > 28
      # we will try and preserve this day
      correct_day_of_month = return_date.day
    else
      correct_day_of_month = nil
    end

    if mo > 0
      mo.times do
        delta = return_date.days_in_month
        return_date += delta * 3600 * 24

        # if the day_of_month is wrong, it must be because we either added PAST
        # the correct month (so roll back), or because we WERE rolled back and
        # when we moved forward a month, we were left back at the smaller day.
        if correct_day_of_month
          if return_date.day < 28
            return_date -= return_date.day * 3600 * 24
          elsif return_date.day < correct_day_of_month
            fix = correct_day_of_month > return_date.days_in_month ? return_date.days_in_month : correct_day_of_month
            return_date += (fix - return_date.day) * 3600 * 24
          end
        end
      end
    else  # mo < 0
      (-mo).times do
        # subtract *last* months number of days.
        # there is a REALLY rare case where subtracting return_date.day is one
        # hour short of "last month" and so you end up with THIS month.  there
        # is NEVER a case when subtracting return_date.day+1 days is NOT
        # "previous month".  dates. :-|  f-em.
        delta = (return_date - (return_date.day+1) * 3600 * 24).days_in_month
        return_date -= delta * 3600 * 24
        # same correction as above
        if correct_day_of_month
          if return_date.day < 28
            return_date -= return_date.day * 3600 * 24
          elsif return_date.day < correct_day_of_month
            fix = correct_day_of_month > return_date.days_in_month ? return_date.days_in_month : correct_day_of_month
            return_date += (fix - return_date.day) * 3600 * 24
          end
        end
      end
    end
  end

  delta = 0
  delta += d * 3600 * 24
  delta += w * 3600 * 24 * 7
  return_date += delta

  # DST adjustment, unless minutes, hours, or seconds were specified.
  #
  # the thinking here is that if they WERE specified, the delta should be
  # accurate to that granularity.  if they were omitted, the hour component
  # should not change, even though an off-by-one adjustment is needed
  #
  # for instance.  3/10/2012 is not in DST.  3/11/2012 IS.
  # Time.at(3/10/2012)  # => 2012-03-10 17:00:00 -0700
  # Time.at(3/10/2012).delta(days:1)  # => 2012-03-11 17:00:00 -0600
  #
  # notice the time is the SAME, even though the time zone is different.  BUT:
  # Time.at(3/10/2012).delta(hours:24)  # => 2012-03-11 17:00:00 -0600
  # Time.at(3/10/2012).delta(hours:25)  # => 2012-03-11 18:00:00 -0600
  unless return_date.dst? == is_dst or is_very_specific
    if is_dst
      return_date += 3600
    else
      return_date -= 3600
    end
  end

  return return_date
end

#downto(last_date, delta = {days: -1}, &block) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 146

def downto(last_date, delta={days: -1}, &block)
  return if last_date > self

  date = self
  while date >= last_date
    if block.arity == 0
      block.call
    else
      block.call(date)
    end
    new_date = date.delta(delta)
    break if new_date >= date
    date = new_date
  end
end

#end_of_dayObject

(main)> t = Time.new

=> 2012-09-27 11:29:12 +0900
(main)> t.end_of_day
=> 2012-09-28 00:00:00 +0900


251
252
253
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 251

def end_of_day
  return self.delta(days: 1).start_of_day
end

#end_of_monthObject

(main)> t = Time.new

=> 2012-09-27 11:29:12 +0900
(main)> t.end_of_month
=> 2012-10-01 00:00:00 +0900


285
286
287
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 285

def end_of_month
  return self.end_of_day.delta(days:days_in_month - day)
end

#end_of_week(start_day = nil) ⇒ Object

(main)> t = Time.new

=> 2012-09-27 11:29:12 +0900
(main)> t.start_of_week
=> 2012-09-30 00:00:00 +0900


268
269
270
271
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 268

def end_of_week(start_day=nil)
  result = self - days_to_week_start(start_day).days + 6.days
  result.end_of_day
end

#eraObject



167
168
169
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 167

def era
  return _calendar_components(NSEraCalendarUnit).era
end

#leap_year?Boolean

Returns:

  • (Boolean)


199
200
201
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 199

def leap_year?
  self.year % 4 == 0 and self.year % 100 != 0 or self.year % 400 == 0
end

#same_day?(other) ⇒ Boolean

Returns:

  • (Boolean)


186
187
188
189
190
191
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 186

def same_day?(other)
  return other.day == self.day &&
         other.month == self.month &&
         other.year == self.year &&
         other.era == self.era
end

#start_of_dayObject

(main)> t = Time.new

=> 2012-09-27 11:29:12 +0900
(main)> t.start_of_day
=> 2012-09-27 00:00:00 +0900


231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 231

def start_of_day
  date_components = NSDateComponents.new
  date_components.hour = 0
  date_components.minute = 0
  date_components.second = 0
  date_components.day = self.day
  date_components.month = self.month
  date_components.year = self.year

  calendar = NSCalendar.alloc.initWithCalendarIdentifier(NSGregorianCalendar)
  calendar.timeZone = NSTimeZone.timeZoneForSecondsFromGMT(self.utc_offset)
  date = calendar.dateFromComponents(date_components)

  return date
end

#start_of_monthObject

(main)> t = Time.new

=> 2012-09-27 11:29:12 +0900
(main)> t.start_of_month
=> 2012-09-01 00:00:00 +0900


277
278
279
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 277

def start_of_month
  return self.start_of_day.delta(days:1 - day)
end

#start_of_week(start_day = nil) ⇒ Object

(main)> t = Time.new

=> 2012-09-27 11:29:12 +0900
(main)> t.start_of_week
=> 2012-09-23 00:00:00 +0900


259
260
261
262
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 259

def start_of_week(start_day=nil)
  result = self - days_to_week_start(start_day).days
  result.start_of_day
end

#string_with_format(format, options = {}) ⇒ Object

Pass in a format string or a Symbol. If you use a Symbol, it must exist in NSDate::SugarCubeFormats.

This method accepts some options:

timezone: String or NSTimeZone. Strings get converted using NSTimeZone.timeZoneWithName unicode: if true, this method will use Unicode date format (TR35) instead of converting to a locale specific format locale: Strings or NSLocale. Strings get converted using NSLocale.localeWithLocaleIdentifier

See <developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DataFormatting/Articles/dfDateFormatting10_4.html#//apple_ref/doc/uid/TP40002369-SW1> and <www.unicode.org/reports/tr35/tr35-19.html#Date_Field_Symbol_Table> for more information about date format strings.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 61

def string_with_format(format, options={})
  timezone = options[:timezone] || NSTimeZone.defaultTimeZone
  if timezone.is_a?(NSString)
    timezone = NSTimeZone.timeZoneWithName(timezone)
  end

  if format.is_a?(Symbol)
    return _string_with_sugarcube_format(format, timezone)
  else
    unicode = options[:unicode]
    if unicode
      return _string_with_unicode_format(format, timezone)
    else
      locale = options[:locale] || NSLocale.currentLocale
      if locale.is_a?(NSString)
        locale = NSLocale.localeWithLocaleIdentifier(locale)
      end

      return _string_with_nsdate_format(format, timezone, locale)
    end
  end
end

#string_with_style(date_style = NSDateFormatterMediumStyle, time_style = NSDateFormatterNoStyle) ⇒ Object



38
39
40
41
42
43
44
45
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 38

def string_with_style(date_style=NSDateFormatterMediumStyle, time_style=NSDateFormatterNoStyle)
  date_formatter = NSDateFormatter.new
  date_style = date_style.nsdatestyle if date_style.is_a? Symbol
  time_style = time_style.nsdatestyle if time_style.is_a? Symbol
  date_formatter.setDateStyle(date_style)
  date_formatter.setTimeStyle(time_style)
  date_formatter.stringFromDate(self)
end

#time_arrayObject

(main)> t = Time.new

=> 2012-09-27 11:29:12 +0900
(main)> t.time_array
=> [11, 29, 12]


215
216
217
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 215

def time_array
  return [self.hour, self.min, self.sec]
end

#timezoneObject Also known as: timeZone



162
163
164
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 162

def timezone
  return _calendar_components(NSTimeZoneCalendarUnit).timeZone
end

#today?Boolean

Returns:

  • (Boolean)


171
172
173
174
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 171

def today?
  today = self.class.new
  return same_day?(today)
end

#tomorrow?Boolean

Returns:

  • (Boolean)


176
177
178
179
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 176

def tomorrow?
  tomorrow = self.class.tomorrow
  return same_day?(tomorrow)
end

#upto(last_date, delta = {days: 1}, &block) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 130

def upto(last_date, delta={days: 1}, &block)
  return if last_date < self

  date = self
  while date <= last_date
    if block.arity == 0
      block.call
    else
      block.call(date)
    end
    new_date = date.delta(delta)
    break if new_date <= date
    date = new_date
  end
end

#utc_offsetObject

In the rare case you actually get an NSDate object - not a Time object - this method is actually useful.



195
196
197
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 195

def utc_offset
  return self.timezone.secondsFromGMT
end

#yesterday?Boolean

Returns:

  • (Boolean)


181
182
183
184
# File 'lib/cocoa/sugarcube-nsdate/nsdate.rb', line 181

def yesterday?
  yesterday = self.class.yesterday
  return same_day?(yesterday)
end