beeminder 0.2.3 → 0.2.4

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/TODO CHANGED
@@ -1,4 +1,5 @@
1
- * TODO [0/3]
1
+ * TODO [0/4]
2
2
  - [ ] add a few more sanity checks
3
3
  - [ ] cache goals / datapoints
4
4
  - [ ] add update function to Datapoint
5
+ - [ ] constructors could be more elegant, but don't wanna break compatibility again, so it can wait until the next major rewrite
data/beeminder.gemspec CHANGED
@@ -16,8 +16,10 @@ Gem::Specification.new do |gem|
16
16
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
17
  gem.require_paths = ["lib"]
18
18
 
19
+ gem.add_dependency 'activesupport', '~> 3.2'
19
20
  gem.add_dependency 'chronic', '~> 0.7'
20
21
  gem.add_dependency 'json'
21
22
  gem.add_dependency 'highline', '~> 1.6'
22
23
  gem.add_dependency 'trollop', '~> 2'
24
+ gem.add_dependency 'tzinfo'
23
25
  end
data/lib/beeminder.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
+ require 'active_support/core_ext'
3
4
  require 'date'
4
5
  require 'json'
5
6
  require 'net/https'
@@ -1,4 +1,4 @@
1
- # coding: utf-8
1
+ # -*- encoding: utf-8 -*-
2
2
 
3
3
  module Beeminder
4
4
  class Goal
@@ -81,7 +81,13 @@ module Beeminder
81
81
  # @return [Array<Beeminder::Datapoint>] returns list of datapoints
82
82
  def datapoints
83
83
  info = @user.get "users/me/goals/#{slug}/datapoints.json"
84
- datapoints = info.map{|d| Datapoint.new d}
84
+ datapoints = info.map do |d_info|
85
+ d_info = {
86
+ :goal => self,
87
+ }.merge(d_info)
88
+
89
+ Datapoint.new d_info
90
+ end
85
91
 
86
92
  datapoints
87
93
  end
@@ -106,8 +112,12 @@ module Beeminder
106
112
  def dial_road dials={}
107
113
  raise "Set exactly two dials." if dials.keys.size != 2
108
114
 
109
- dials["goaldate"] = dials["goaldate"].strftime('%s') unless dials["goaldate"].nil?
110
-
115
+ # convert to proper timestamp
116
+ unless dials["goaldate"].nil?
117
+ dials["goaldate"] = @user.convert_to_timezone dials["goaldate"] if @user.enforce_timezone?
118
+ dials["goaldate"] = dials["goaldate"].strftime('%s')
119
+ end
120
+
111
121
  @user.post "users/me/goals/#{@slug}/dial_road.json", dials
112
122
  end
113
123
 
@@ -117,12 +127,15 @@ module Beeminder
117
127
  def add datapoints, opts={}
118
128
  datapoints = [*datapoints]
119
129
 
120
- # FIXME create_all doesn't work because Ruby's POST encoding of arrays is broken.
121
130
  datapoints.each do |dp|
131
+ # we grab these datapoints for ourselves
132
+ dp.goal = self
133
+
122
134
  data = {
123
135
  "sendmail" => opts[:sendmail] || false
124
136
  }.merge(dp.to_hash)
125
137
 
138
+ # TODO create_all doesn't work because Ruby's POST encoding of arrays is broken.
126
139
  @user.post "users/me/goals/#{@slug}/datapoints.json", data
127
140
  end
128
141
  end
@@ -160,10 +173,10 @@ module Beeminder
160
173
  end
161
174
 
162
175
  # some conversions
163
- @goaldate = DateTime.strptime(@goaldate.to_s, '%s') unless @goaldate.nil?
176
+ @goaldate = DateTime.strptime(@goaldate.to_s, '%s').in_time_zone(@user.timezone) unless @goaldate.nil?
164
177
  @goal_type = @goal_type.to_sym unless @goal_type.nil?
165
- @losedate = DateTime.strptime(@losedate.to_s, '%s') unless @losedate.nil?
166
- @updated_at = DateTime.strptime(@updated_at.to_s, '%s')
178
+ @losedate = DateTime.strptime(@losedate.to_s, '%s').in_time_zone(@user.timezone) unless @losedate.nil?
179
+ @updated_at = DateTime.strptime(@updated_at.to_s, '%s').in_time_zone(@user.timezone)
167
180
  end
168
181
  end
169
182
 
@@ -183,6 +196,10 @@ module Beeminder
183
196
  # @return [DateTime] The time that this datapoint was entered or last updated.
184
197
  attr_reader :updated_at
185
198
 
199
+ # @return [Beeminder::Goal] Goal this datapoint belongs to.
200
+ # Optional for new datapoints. Use `Goal#add` to add new datapoints to a goal.
201
+ attr_accessor :goal
202
+
186
203
  def initialize info={}
187
204
  # set variables
188
205
  info.each do |k,v|
@@ -196,11 +213,21 @@ module Beeminder
196
213
  # some conversions
197
214
  @timestamp = DateTime.strptime(@timestamp.to_s, '%s') unless @timestamp.is_a?(Date) || @timestamp.is_a?(Time)
198
215
  @updated_at = DateTime.strptime(@updated_at.to_s, '%s') unless @updated_at.nil?
216
+
217
+ # set timezone if possible
218
+ unless @goal.nil?
219
+ @timestamp = @timestamp.in_time_zone @goal.user.timezone
220
+ @updated_at = @updated_at.in_time_zone @goal.user.timezone
221
+ end
199
222
  end
200
223
 
201
224
  # Convert datapoint to hash for POSTing.
202
225
  # @return [Hash]
203
226
  def to_hash
227
+ if not @goal.nil? and @goal.user.enforce_timezone?
228
+ @timestamp = @goal.user.convert_to_timezone @timestamp
229
+ end
230
+
204
231
  {
205
232
  "timestamp" => @timestamp.strftime('%s'),
206
233
  "value" => @value || 0,
@@ -1,4 +1,4 @@
1
- # coding: utf-8
1
+ # -*- encoding: utf-8 -*-
2
2
 
3
3
  module Beeminder
4
4
  class User
@@ -16,14 +16,19 @@ module Beeminder
16
16
 
17
17
  # @return [Symbol] Type of user, can be `:personal` (default) or `:oauth`.
18
18
  attr_reader :auth_type
19
+
20
+ # @return [true|false] Enforce user timezone for all passed times? Should be true unless you know what you're doing. (Default: `true`.)
21
+ attr_accessor :enforce_timezone
19
22
 
20
23
  def initialize token, opts={}
21
24
  opts = {
22
25
  :auth_type => :personal,
26
+ :enforce_timezone => true,
23
27
  }.merge(opts)
24
28
 
25
- @token = token
26
- @auth_type = opts[:auth_type]
29
+ @token = token
30
+ @auth_type = opts[:auth_type]
31
+ @enforce_timezone = opts[:enforce_timezone]
27
32
 
28
33
  @token_type =
29
34
  case @auth_type
@@ -38,14 +43,21 @@ module Beeminder
38
43
  info = get "users/me.json"
39
44
 
40
45
  @name = info["username"]
41
- @timezone = info["timezone"]
42
- @updated_at = DateTime.strptime(info["updated_at"].to_s, '%s')
46
+ @timezone = info["timezone"] || "UTC"
47
+ @updated_at = DateTime.strptime(info["updated_at"].to_s, '%s').in_time_zone(@timezone)
43
48
  end
44
49
 
50
+ # Enforce timezone for all passed times?
51
+ #
52
+ # @return [true|false]
53
+ def enforce_timezone?
54
+ !!@enforce_timezone
55
+ end
56
+
45
57
  # List of goals.
46
58
  #
47
59
  # @param filter [Symbol] filter goals, can be `:all` (default), `:frontburner` or `:backburner`
48
- # @ return [Array<Beeminder::Goal>] returns list of goals
60
+ # @return [Array<Beeminder::Goal>] returns list of goals
49
61
  def goals filter=:all
50
62
  raise "invalid goal filter: #{filter}" unless [:all, :frontburner, :backburner].include? filter
51
63
 
@@ -115,8 +127,25 @@ module Beeminder
115
127
  _connection :put, cmd, data
116
128
  end
117
129
 
118
- private
130
+ # Converts time object to one with user's timezone.
131
+ #
132
+ # @param time [Date|DateTime|Time] Time to convert.
133
+ # @return [Time] Converted time.
134
+ def convert_to_timezone time
135
+ # TODO seems way too hack-ish
136
+ old_tz = Time.zone
137
+ Time.zone = @timezone
138
+
139
+ time = time.to_time
140
+ t = Time.new(time.year, time.month, time.day, time.hour, time.min, time.sec)
141
+
142
+ Time.zone = old_tz
143
+
144
+ t
145
+ end
119
146
 
147
+ private
148
+
120
149
  # Establish HTTPS connection to API.
121
150
  def _connection type, cmd, data
122
151
  api = "https://www.beeminder.com/api/v1/#{cmd}"
@@ -128,7 +157,7 @@ module Beeminder
128
157
  http.use_ssl = true
129
158
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE # FIXME: actually verify
130
159
 
131
- # FIXME Sanity check for wrong timestamp. Source of bug unknown, so we prevent screwing up someone's graph.
160
+ # FIXME Sanity check for wrong timestamp. Source of bug unknown, but at least we can prevent screwing up someone's graph.
132
161
  unless data["timestamp"].nil?
133
162
  if not data["timestamp"].match(/^\d+$/) or data["timestamp"] < "1280000000"
134
163
  raise ArgumentError, "invalid timestamp: #{data["timestamp"]}"
@@ -1,3 +1,3 @@
1
1
  module Beeminder
2
- VERSION = "0.2.3"
2
+ VERSION = "0.2.4"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: beeminder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-16 00:00:00.000000000 Z
12
+ date: 2012-09-19 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.2'
14
30
  - !ruby/object:Gem::Dependency
15
31
  name: chronic
16
32
  requirement: !ruby/object:Gem::Requirement
@@ -75,6 +91,22 @@ dependencies:
75
91
  - - ~>
76
92
  - !ruby/object:Gem::Version
77
93
  version: '2'
94
+ - !ruby/object:Gem::Dependency
95
+ name: tzinfo
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
78
110
  description: Convenient access to Beeminder's API.
79
111
  email:
80
112
  - mail@muflax.com
@@ -110,7 +142,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
110
142
  version: '0'
111
143
  segments:
112
144
  - 0
113
- hash: -4420533533001191387
145
+ hash: 4040090933613779056
114
146
  required_rubygems_version: !ruby/object:Gem::Requirement
115
147
  none: false
116
148
  requirements:
@@ -119,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
151
  version: '0'
120
152
  segments:
121
153
  - 0
122
- hash: -4420533533001191387
154
+ hash: 4040090933613779056
123
155
  requirements: []
124
156
  rubyforge_project:
125
157
  rubygems_version: 1.8.23