sugarcube 0.8.7 → 0.9

Sign up to get free protection for your applications and to get access to all the features.
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