sugarcube 0.8.7 → 0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -88,43 +88,38 @@ end
88
88
  NSDate
89
89
  --------
90
90
 
91
- NSDate does not make it easy to get the sensible attributes of your datetime
92
- object. This *does* make sense, if you are truly thinking globally, but I am
93
- not. :-P
94
-
95
- Adds the following methods to get a date component: `year, month, day, hour, minute, second, yms, hms, datetime`
96
-
97
- `ymd`, `hms`, and `datetime` return arrays, so that comparing dates, times, or
98
- both become simple `date1.ymd == date2.ymd`. If you need to *compare* dates,
99
- use the Foundation method `date1.compare(date2)` => `-1 or 0 or 1`.
100
-
101
- ```
102
- (main)> now = NSDate.new
91
+ `NSDate` objects are converted to `Time` objects automatically by rubymotion.
92
+ That's the good news. The bad news? That still doesn't help a lot with some of
93
+ the everyday date&time crap we have to deal with. (I hate dates, especially
94
+ recurring events)
95
+
96
+ 1. Adds the following methods to get date and time components: `date, time, datetime`.
97
+
98
+ These methods return arrays. Comparing dates, times, or both become
99
+ simple `date1.date == date2.date`.
100
+ 2. While I would love to support `date + 1.month` and have that support "smart"
101
+ calendar math (e.g. "2/13/2013" + 1.month => "3/13/2013"), I can't fudge with
102
+ the return value of `1.month` (=> `Fixnum`), and I won't make the terrible
103
+ assumption that "30 days of seconds is *about* one month". So instead, a new
104
+ method that accepts date components as options is introduced. `date.delta(month:1)`
105
+ 3. Something that is often done is checking whether two dates are the same,
106
+ ignoring the time components. `start_of_day` and `end_of_day` methods help
107
+ you here. They are akin to `floor` and `ceil`, if you consider the time to
108
+ be the "floating" component, and the date to be the nearest "integer".
109
+
110
+ ```
111
+ (main)> now = NSDate.new # Time.new is the same thing
103
112
  => 2012-09-13 09:19:06 -0600
104
- (main)> now.year
105
- => 2012
106
- (main)> now.month
107
- => 9
108
- (main)> now.day
109
- => 13
110
- (main)> now.hour
111
- => 9
112
- (main)> now.minute
113
- => 19
114
- (main)> now.second
115
- => 6
116
- (main)> now.weekday
117
- => 5
118
- (main)> now.ymd
113
+ (main)> now.date
119
114
  => [2012, 9, 13]
120
- (main)> now.hms
115
+ (main)> now.time
121
116
  => [9, 19, 6]
122
117
  (main)> now.datetime
123
118
  => [2012, 9, 13, 9, 19, 6]
124
119
  ```
125
120
 
126
- And it is easy to add seconds to the date, and don't forget about the
127
- time-related methods added to `Numeric`!
121
+ And it is easy to add seconds to the date using the time-related methods added
122
+ to `Numeric`, and the useful `start_of_day`/`end_of_day` methods.
128
123
 
129
124
  ```ruby
130
125
  (main)> now + 5
@@ -135,11 +130,15 @@ time-related methods added to `Numeric`!
135
130
  => 2012-09-13 09:24:06 -0600
136
131
  (main)> now + 5.days
137
132
  => 2012-09-18 09:19:06 -0600
133
+ (main)> now.start_of_day
134
+ => 2012-09-13 00:00:00 -0600
135
+ (main)> now.end_of_day
136
+ => 2012-09-14 00:00:00 -0600
138
137
  ```
139
138
 
140
- Time zone objects are available, but the `utc_offset` is a little more
141
- immediately useful. It returns the offset *in seconds*, so divide by `1.0.hour`
142
- to get the offset in hours.
139
+ Time zone objects are available, but the `Time#utc_offset` method is a little
140
+ more useful. It returns the offset *in seconds*, so divide by `1.0.hour` to get
141
+ the offset in hours. `utc_offset` is built into `Time`, not added by SugarCube.
143
142
 
144
143
  ```ruby
145
144
  (main)> now.timezone
@@ -148,10 +147,63 @@ to get the offset in hours.
148
147
  => "America/Denver"
149
148
  (main)> now.utc_offset
150
149
  => -21600
151
- (main)> now.utc_offset / 1.0.hour
152
- => -6.0
150
+ (main)> now.utc_offset / 1.hour
151
+ => -6
153
152
  ```
154
153
 
154
+ The `delta` method is smart.
155
+
156
+ ```ruby
157
+ (main)> feb_28_2012_stamp = 1330473600 # what, you don't have this memorized?
158
+ => 1330473600
159
+ (main)> feb_28_2012 = Time.at(feb_28_2012_stamp)
160
+ => 2012-02-28 17:00:00 -0700
161
+
162
+ (main)> feb_28_2012.delta(hours:1)
163
+ => 2012-02-28 18:00:00 -0700
164
+ (main)> feb_28_2012.delta(hours:2)
165
+ => 2012-02-28 19:00:00 -0700
166
+
167
+ (main)> feb_28_2012.delta(days:1)
168
+ => 2012-02-29 17:00:00 -0700
169
+ (main)> feb_28_2012.delta(days:2)
170
+ => 2012-03-01 17:00:00 -0700
171
+
172
+ (main)> feb_28_2012.delta(months:1)
173
+ => 2012-03-28 17:00:00 -0600 # look, the time didn't change, event though there was a DST change!
174
+ (main)> feb_28_2012.delta(months:1, hours:0)
175
+ => 2012-03-28 18:00:00 -0600 # disable the DST fix by specifying hours, minutes, or seconds (a "precise" delta)
176
+
177
+ (main)> feb_28_2012.delta(years:1)
178
+ => 2013-02-28 17:00:00 -0700
179
+ (main)> feb_28_2012.delta(days:1, years:1)
180
+ => 2013-02-28 17:00:00 -0700
181
+ (main)> feb_28_2012.delta(days:1, years:1, months:1)
182
+ => 2013-03-29 17:00:00 -0600
183
+
184
+ (main)> jan_29_2013 = feb_28_2012.delta(days:1, months:11)
185
+ => 2013-01-29 17:00:00 -0700
186
+
187
+ # what is 1/29/2013 plus two months? easy! march 29, 2013
188
+ (main)> jan_29_2013.delta(months:2)
189
+ => 2013-03-29 17:00:00 -0600
190
+
191
+ # yeah, smart guy? well then what is 1/29/2013 plus ONE month.
192
+ # it's feb 28th. trust me. when someone says "see you in a month!"
193
+ # they mean "next month", not "in the early part of two months in the future",
194
+ # which is where the math will take you if you don't add a "day of month" correction.
195
+ (main)> jan_29_2013.delta(months:1)
196
+ => 2013-02-28 17:00:00 -0700
197
+
198
+ # does it work in reverse? fuuuuuu...
199
+ (main)> jan_29_2013.delta(months:-11)
200
+ => 2012-02-29 17:00:00 -0700
201
+ # ...ck yeah! :-)
202
+
203
+ # unfortunately you will end up with stuff like this:
204
+ (main)> feb_28_2012 == feb_28_2012.delta(days:1, months:12).delta(months:-12)
205
+ => true
206
+ ```
155
207
 
156
208
  NSURL
157
209
  -------
@@ -465,16 +517,20 @@ after 1.minute do
465
517
  end
466
518
 
467
519
  # other time-related methods
520
+ # for compatibility with Time methods, the mins/secs (and min/sec) aliases are provided. Personally,
521
+ # I like the more verbose minutes/seconds.
468
522
  1.second || 2.seconds
469
- 1.minute || 2.minutes # 60 seconds
470
- 1.hour || 2.hours # 60 minutes
471
- 1.day || 2.days # 24 hours
472
- 1.week || 2.weeks # 7 days
473
- # with sensible values for 'month' and 'year', even though we all know you can't
523
+ 1.sec || 2.secs # aliases
524
+ 1.minute || 2.minutes # 1.minute = 60 seconds
525
+ 1.min || 2.mins # aliases
526
+ 1.hour || 2.hours # 1.hour = 60 minutes
527
+ 1.day || 2.days # 1.day = 24 hours
528
+ 1.week || 2.weeks # 1.week = 7 days
529
+ # sensible values for 'month' and 'year', even though we all know you can't
474
530
  # **really** define them this way (go back to python if you find your brain
475
531
  # hemorrhaging):
476
- 1.month || 2.months # 30 days
477
- 1.year || 2.years # 365 days
532
+ 1.month || 2.months # 1.month = 30 days
533
+ 1.year || 2.years # 1.year = 365 days
478
534
  ```
479
535
 
480
536
  NSUserDefaults
@@ -1,62 +1,49 @@
1
1
  class NSDate
2
- def year
3
- return components(NSYearCalendarUnit).year
2
+ def timezone
3
+ return _calendar_components(NSTimeZoneCalendarUnit).timeZone
4
4
  end
5
+ alias timeZone timezone
5
6
 
6
- def month
7
- return components(NSMonthCalendarUnit).month
7
+ def utc_offset
8
+ return self.timezone.secondsFromGMT
8
9
  end
9
10
 
10
- def day
11
- return components(NSDayCalendarUnit).day
11
+ def leap?
12
+ self.year % 4 == 0 and self.year % 100 != 0 or self.year % 400 == 0
12
13
  end
13
14
 
14
- def ymd
15
+ def date
15
16
  return [self.year, self.month, self.day]
16
17
  end
17
18
 
18
- def weekday
19
- return components(NSWeekdayCalendarUnit).weekday
20
- end
21
-
22
- def hour
23
- return components(NSHourCalendarUnit).hour
24
- end
25
-
26
- def minute
27
- return components(NSMinuteCalendarUnit).minute
19
+ def time
20
+ return [self.hour, self.min, self.sec]
28
21
  end
29
22
 
30
- def second
31
- return components(NSSecondCalendarUnit).second
32
- end
33
-
34
- def timezone
35
- return components(NSTimeZoneCalendarUnit).timeZone
23
+ def datetime
24
+ return [self.year, self.month, self.day, self.hour, self.min, self.sec]
36
25
  end
37
- alias timeZone timezone
38
26
 
39
- def utc_offset
40
- return self.timezone.secondsFromGMT
27
+ def start_of_day
28
+ time_interval = self.hour.hours + self.min.minutes + self.sec
29
+ return self - time_interval
41
30
  end
42
31
 
43
- def hms
44
- return [self.hour, self.minute, self.second]
32
+ def end_of_day
33
+ time_interval = (23 - self.hour).hours + (59 - self.min).minutes + 60 - self.sec
34
+ return self + time_interval
45
35
  end
46
36
 
47
- def datetime
48
- return [self.year, self.month, self.day, self.hour, self.minute, self.second]
37
+ def days_in_month
38
+ NSCalendar.currentCalendar.rangeOfUnit(NSDayCalendarUnit, inUnit:NSMonthCalendarUnit, forDate:self).length
49
39
  end
50
40
 
51
- def +(time_interval)
52
- return self.dateByAddingTimeInterval(time_interval)
41
+ def days_in_year
42
+ NSCalendar.currentCalendar.rangeOfUnit(NSDayCalendarUnit, inUnit:NSYearCalendarUnit, forDate:self).length
53
43
  end
54
44
 
55
45
  private
56
- def components(components)
57
- unless (@@calendar ||= nil)
58
- @@calendar = NSCalendar.alloc.initWithCalendarIdentifier(NSGregorianCalendar)
59
- end
60
- return @@calendar.components(components, fromDate:self)
46
+ def _calendar_components(components)
47
+ return NSCalendar.currentCalendar.components(components, fromDate:self)
61
48
  end
62
49
  end
@@ -0,0 +1,114 @@
1
+ class NSDate
2
+
3
+ def +(val)
4
+ if val.is_a?(Hash)
5
+ return self.delta(val)
6
+ end
7
+ super
8
+ end
9
+
10
+ def delta(_components)
11
+ components = {}.update(_components)
12
+ is_very_specific = components.has_key?(:seconds)
13
+ is_very_specific ||= components.has_key?(:minutes)
14
+ is_very_specific ||= components.has_key?(:hours)
15
+
16
+ y = components.delete(:years) || 0
17
+ mo = components.delete(:months) || 0
18
+ d = components.delete(:days) || 0
19
+ h = components.delete(:hours) || 0
20
+ mi = components.delete(:minutes) || 0
21
+ s = components.delete(:seconds) || 0
22
+ w = components.delete(:weeks) || 0
23
+ raise "Unknown arguments #{components.keys}" unless components.empty?
24
+
25
+ is_dst = self.dst?
26
+
27
+ delta = s
28
+ # todo: leap second adjustment? can leap seconds be detected?
29
+ delta += mi.minutes
30
+ delta += h.hours
31
+
32
+ delta += d.days
33
+ delta += w.weeks
34
+ return_date = self + delta
35
+
36
+ # using days_in_month, this is pretty easy. 12 mos per year IS a constant,
37
+ # and then we just keep adding the number of days in the month (or last month
38
+ # if we're going backwards). The curve ball is that when we are in day
39
+ # 29,30,31, we might jump forward a month and "fall off" to the next month.
40
+ # In this case, we add a correction. We will always move forwards or
41
+ # backwards until the return_date.day is correct.
42
+ mo += y * 12
43
+ if mo != 0
44
+ if return_date.day > 28
45
+ # we will try and preserve this day
46
+ correct_day_of_month = return_date.day
47
+ else
48
+ correct_day_of_month = nil
49
+ end
50
+
51
+ if mo > 0
52
+ mo.times do
53
+ delta = return_date.days_in_month
54
+ return_date += delta.days
55
+
56
+ # if the day_of_month is wrong, it must be because we either added PAST
57
+ # the correct month (so roll back), or because we WERE rolled back and
58
+ # when we moved forward a month, we were left back at the smaller day.
59
+ if correct_day_of_month
60
+ if return_date.day < 28
61
+ return_date -= return_date.day.days
62
+ elsif return_date.day < correct_day_of_month
63
+ fix = correct_day_of_month > return_date.days_in_month ? return_date.days_in_month : correct_day_of_month
64
+ return_date += (fix - return_date.day).days
65
+ end
66
+ end
67
+ end
68
+ elsif mo < 0
69
+ (-mo).times do
70
+ # subtract *last* months number of days.
71
+ # there is a REALLY rare case where subtracting return_date.day is one
72
+ # hour short of "last month" and so you end up with THIS month. there
73
+ # is NEVER a case when subtracting return_date.day+1 days is NOT
74
+ # "previous month". dates. :-| f-em.
75
+ delta = (return_date - (return_date.day+1).days).days_in_month
76
+ return_date -= delta.days
77
+ # same correction as above
78
+ if correct_day_of_month
79
+ if return_date.day < 28
80
+ return_date -= return_date.day.days
81
+ elsif return_date.day < correct_day_of_month
82
+ fix = correct_day_of_month > return_date.days_in_month ? return_date.days_in_month : correct_day_of_month
83
+ return_date += (fix - return_date.day).days
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ # DST adjustment, unless minutes, hours, or seconds were specified.
91
+ #
92
+ # the thinking here is that if they WERE specified, the delta should be
93
+ # accurate to that granularity. if they were omitted, the hour component
94
+ # should not change, even though an off-by-one adjustment is needed
95
+ #
96
+ # for instance. 3/10/2012 is not in DST. 3/11/2012 IS.
97
+ # Time.at(3/10/2012) # => 2012-03-10 17:00:00 -0700
98
+ # Time.at(3/10/2012).delta(days:1) # => 2012-03-11 17:00:00 -0600
99
+ #
100
+ # notice the time is the SAME, even though the time zone is different. BUT:
101
+ # Time.at(3/10/2012).delta(hours:24) # => 2012-03-11 17:00:00 -0600
102
+ # Time.at(3/10/2012).delta(hours:25) # => 2012-03-11 18:00:00 -0600
103
+ unless return_date.dst? == is_dst or is_very_specific
104
+ if is_dst
105
+ return_date += 1.hour
106
+ else
107
+ return_date -= 1.hour
108
+ end
109
+ end
110
+
111
+ return return_date
112
+ end
113
+
114
+ end
@@ -8,11 +8,15 @@ class Numeric
8
8
  self
9
9
  end
10
10
  alias second seconds
11
+ alias sec seconds
12
+ alias secs seconds
11
13
 
12
14
  def minutes
13
15
  self * 60
14
16
  end
15
17
  alias minute minutes
18
+ alias min minutes
19
+ alias mins minutes
16
20
 
17
21
  def hours
18
22
  self * 3600
@@ -1,3 +1,3 @@
1
1
  module SugarCube
2
- Version = '0.8.7'
2
+ Version = '0.9'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sugarcube
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.7
4
+ version: '0.9'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-09-13 00:00:00.000000000 Z
13
+ date: 2012-09-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rake
17
- requirement: &70310166918200 !ruby/object:Gem::Requirement
17
+ requirement: &70323060274480 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: '0'
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70310166918200
25
+ version_requirements: *70323060274480
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rspec
28
- requirement: &70310166917660 !ruby/object:Gem::Requirement
28
+ requirement: &70323060273940 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,7 +33,7 @@ dependencies:
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *70310166917660
36
+ version_requirements: *70323060273940
37
37
  description: ! '== Description
38
38
 
39
39
 
@@ -75,6 +75,7 @@ files:
75
75
  - lib/sugarcube/fixnum.rb
76
76
  - lib/sugarcube/notifications.rb
77
77
  - lib/sugarcube/nsdate.rb
78
+ - lib/sugarcube/nsdate_delta.rb
78
79
  - lib/sugarcube/nsindexpath.rb
79
80
  - lib/sugarcube/nsindexset.rb
80
81
  - lib/sugarcube/nsstring.rb