green-button-data 0.1.0

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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +11 -0
  5. data/Guardfile +50 -0
  6. data/LICENSE.txt +26 -0
  7. data/README.md +72 -0
  8. data/Rakefile +5 -0
  9. data/green-button-data.gemspec +24 -0
  10. data/lib/green-button-data/core_ext/date.rb +9 -0
  11. data/lib/green-button-data/core_ext/fixnum.rb +10 -0
  12. data/lib/green-button-data/core_ext.rb +2 -0
  13. data/lib/green-button-data/dst.rb +49 -0
  14. data/lib/green-button-data/enumerations.rb +523 -0
  15. data/lib/green-button-data/feed.rb +7 -0
  16. data/lib/green-button-data/parser/application_information.rb +180 -0
  17. data/lib/green-button-data/parser/authorization.rb +45 -0
  18. data/lib/green-button-data/parser/content.rb +36 -0
  19. data/lib/green-button-data/parser/entry.rb +47 -0
  20. data/lib/green-button-data/parser/feed.rb +47 -0
  21. data/lib/green-button-data/parser/interval.rb +32 -0
  22. data/lib/green-button-data/parser/interval_block.rb +32 -0
  23. data/lib/green-button-data/parser/interval_reading.rb +30 -0
  24. data/lib/green-button-data/parser/local_time_parameters.rb +105 -0
  25. data/lib/green-button-data/parser/rational_number.rb +22 -0
  26. data/lib/green-button-data/parser/reading_type.rb +134 -0
  27. data/lib/green-button-data/parser/service_category.rb +17 -0
  28. data/lib/green-button-data/parser/usage_point.rb +20 -0
  29. data/lib/green-button-data/parser.rb +2 -0
  30. data/lib/green-button-data/utilities.rb +106 -0
  31. data/lib/green-button-data/version.rb +3 -0
  32. data/lib/green-button-data.rb +24 -0
  33. data/spec/fixtures/ESPIApplicationInformation.xml +49 -0
  34. data/spec/fixtures/ESPIAuthorization.xml +25 -0
  35. data/spec/fixtures/ESPIIntervalBlock.xml +3814 -0
  36. data/spec/fixtures/ESPILocalTimeParameters.xml +16 -0
  37. data/spec/fixtures/ESPIReadingType.xml +535 -0
  38. data/spec/fixtures/ESPIUsagePoint.xml +18 -0
  39. data/spec/fixtures.rb +22 -0
  40. data/spec/green-button-data/core_ext/date_spec.rb +18 -0
  41. data/spec/green-button-data/core_ext/fixnum_spec.rb +13 -0
  42. data/spec/green-button-data/parser/application_information_spec.rb +154 -0
  43. data/spec/green-button-data/parser/authorization_spec.rb +52 -0
  44. data/spec/green-button-data/parser/interval_block_spec.rb +39 -0
  45. data/spec/green-button-data/parser/local_time_parameter_spec.rb +56 -0
  46. data/spec/green-button-data/parser/reading_type_spec.rb +76 -0
  47. data/spec/green-button-data/parser/usage_point_spec.rb +22 -0
  48. data/spec/green-button-data/utilities_spec.rb +63 -0
  49. data/spec/spec_helper.rb +20 -0
  50. data/spec/support/custom_expectations/warn_expectation.rb +31 -0
  51. metadata +148 -0
@@ -0,0 +1,45 @@
1
+ module GreenButtonData
2
+ module Parser
3
+ class Authorization
4
+ include SAXMachine
5
+ include Utilities
6
+
7
+ element :authorizedPeriod, class: Interval, as: :authorized_period
8
+ element :publishedPeriod, class: Interval, as: :published_period
9
+
10
+ element :expires_at, class: Integer do |epoch|
11
+ Time.at(normalize_epoch(epoch)).utc.to_datetime
12
+ end
13
+
14
+ element :status, class: Integer
15
+
16
+ def active?
17
+ @status > 0
18
+ end
19
+
20
+ # TODO: Add scope parser
21
+ element :scope
22
+
23
+ element :resourceURI, as: :resource_uri
24
+ element :authorizationURI, as: :authorization_uri
25
+
26
+ # ESPI Namespacing
27
+ element :'espi:authorizedPeriod', class: Interval, as: :authorized_period
28
+ element :'espi:publishedPeriod', class: Interval, as: :published_period
29
+ element :'espi:expires_at', class: Integer, as: :expires_at
30
+ element :'espi:status', class: Integer, as: :status
31
+ element :'espi:scope', as: :scope
32
+ element :'espi:resourceURI', as: :resource_uri
33
+ element :'espi:authorizationURI', as: :authorization_uri
34
+
35
+ # Special case for PG&E which uses generic namespacing
36
+ element :'ns0:authorizedPeriod', class: Interval, as: :authorized_period
37
+ element :'ns0:publishedPeriod', class: Interval, as: :published_period
38
+ element :'ns0:expires_at', class: Integer, as: :expires_at
39
+ element :'ns0:status', class: Integer, as: :status
40
+ element :'ns0:scope', as: :scope
41
+ element :'ns0:resourceURI', as: :resource_uri
42
+ element :'ns0:authorizationURI', as: :authorization_uri
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,36 @@
1
+ module GreenButtonData
2
+ module Parser
3
+ class Content
4
+ include SAXMachine
5
+
6
+ element :ApplicationInformation, class: ApplicationInformation,
7
+ as: :application_information
8
+ element :Authorization, class: Authorization, as: :authorization
9
+ element :IntervalBlock, class: IntervalBlock, as: :interval_block
10
+ element :LocalTimeParameters, class: LocalTimeParameters,
11
+ as: :local_time_parameters
12
+ element :ReadingType, class: ReadingType, as: :reading_type
13
+ element :UsagePoint, class: UsagePoint, as: :usage_point
14
+
15
+ # ESPI Namespacing
16
+ element :'espi:ApplicationInformation', class: ApplicationInformation,
17
+ as: :application_information
18
+ element :'espi:Authorization', class: Authorization, as: :authorization
19
+ element :'espi:IntervalBlock', class: IntervalBlock, as: :interval_block
20
+ element :'espi:LocalTimeParameters', class: LocalTimeParameters,
21
+ as: :local_time_parameters
22
+ element :'espi:ReadingType', class: ReadingType, as: :reading_type
23
+ element :'espi:UsagePoint', class: UsagePoint, as: :usage_point
24
+
25
+ # Special case for PG&E generic namespaces
26
+ element :'ns0:ApplicationInformation', class: ApplicationInformation,
27
+ as: :application_information
28
+ element :'ns0:Authorization', class: Authorization, as: :authorization
29
+ element :'ns0:IntervalBlock', class: IntervalBlock, as: :interval_block
30
+ element :'ns0:LocalTimeParameters', class: LocalTimeParameters,
31
+ as: :local_time_parameters
32
+ element :'ns0:ReadingType', class: ReadingType, as: :reading_type
33
+ element :'ns0:UsagePoint', class: UsagePoint, as: :usage_point
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,47 @@
1
+ module GreenButtonData
2
+ module Parser
3
+ class Entry
4
+ include SAXMachine
5
+ include Utilities
6
+
7
+ element :id, as: :entry_id
8
+
9
+ def id
10
+ @entry_id ||= @self
11
+ end
12
+
13
+ element :link, as: :up, value: :href, with: { rel: 'up' }
14
+ element :link, as: :self, value: :href, with: { rel: 'self' }
15
+ element :link, as: :related, value: :href, with: { rel: 'related' }
16
+
17
+ element :content, class: Content, as: :content
18
+
19
+ # Published Date
20
+ element :published
21
+
22
+ def published
23
+ @published ||= @updated
24
+ end
25
+
26
+ def published=(val)
27
+ @published = parse_datetime val
28
+ end
29
+
30
+ # Updated Date
31
+ element :updated
32
+
33
+ def updated=(val)
34
+ @updated = parse_datetime val
35
+ end
36
+
37
+ # Handle PG&E namespacing
38
+ element :'ns1:id', as: :entry_id
39
+ element :'ns1:link', as: :up, value: :href, with: { rel: 'up' }
40
+ element :'ns1:link', as: :self, value: :href, with: { rel: 'self' }
41
+ element :'ns1:link', as: :related, value: :href, with: { rel: 'related' }
42
+ element :'ns1:content', class: Content, as: :content
43
+ element :'ns1:published', as: :published
44
+ element :'ns1:updated', as: :updated
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,47 @@
1
+ module GreenButtonData
2
+ module Parser
3
+ class Feed
4
+ include SAXMachine
5
+ include Utilities
6
+
7
+ element :id, as: :feed_id
8
+
9
+ def id
10
+ @feed_id ||= @feed_url
11
+ end
12
+
13
+ element :title
14
+ element :subtitle, as: :description
15
+
16
+ element :link, as: :url, value: :href, with: { type: 'text/html' }
17
+ element :link, as: :feed_url, value: :href, with: { rel: 'self' }
18
+ element :link, as: :links, value: :href
19
+
20
+ def url
21
+ @url || (links - [feed_url]).last || links.last
22
+ end
23
+
24
+ def feed_url
25
+ @feed_url ||= links.first
26
+ end
27
+
28
+ elements :entry, class: Entry, as: :entries
29
+
30
+ element :updated
31
+
32
+ def updated=(val)
33
+ @updated = parse_datetime val
34
+ end
35
+
36
+ # PG&E's generic namespace
37
+ element :'ns1:id', as: :feed_id
38
+ element :'ns1:title', as: :title
39
+ element :'ns1:subtitle', as: :description
40
+ element :'ns1:link', as: :url, value: :href, with: { type: 'text/html' }
41
+ element :'ns1:link', as: :feed_url, value: :href, with: { rel: 'self' }
42
+ element :'ns1:links', as: :links, value: :href
43
+ element :'ns1:entry', class: Entry, as: :entries
44
+ element :'ns1:updated', as: :updated
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,32 @@
1
+ module GreenButtonData
2
+ module Parser
3
+ class Interval
4
+ include SAXMachine
5
+ include Utilities
6
+
7
+ element :duration, class: Integer do |t|
8
+ normalize_epoch t
9
+ end
10
+
11
+ element :start, class: Integer do |t|
12
+ normalize_epoch t
13
+ end
14
+
15
+ def starts_at
16
+ Time.at(normalize_epoch(@start)).utc.to_datetime
17
+ end
18
+
19
+ def ends_at
20
+ Time.at(normalize_epoch(@start + @duration)).utc.to_datetime
21
+ end
22
+
23
+ # Standard ESPI namespacing
24
+ element :'espi:duration', class: Integer, as: :duration
25
+ element :'espi:start', class: Integer, as: :start
26
+
27
+ # Special case for PG&E which uses generic namespacing
28
+ element :'ns0:duration', class: Integer, as: :duration
29
+ element :'ns0:start', class: Integer, as: :start
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ module GreenButtonData
2
+ module Parser
3
+ class IntervalBlock
4
+ include SAXMachine
5
+
6
+ element :interval, class: Interval
7
+ elements :IntervalReading, class: IntervalReading, as: :interval_readings
8
+
9
+ def duration
10
+ @interval.duration
11
+ end
12
+
13
+ def starts_at
14
+ @interval.starts_at
15
+ end
16
+
17
+ def ends_at
18
+ @interval.ends_at
19
+ end
20
+
21
+ # Standard ESPI namespacing
22
+ element :'espi:interval', class: Interval, as: :interval
23
+ elements :'espi:IntervalReading', class: IntervalReading,
24
+ as: :interval_readings
25
+
26
+ # Special case for PG&E which uses generic namespacing
27
+ element :'ns0:interval', class: Interval, as: :interval
28
+ elements :'ns0:IntervalReading', class: IntervalReading,
29
+ as: :interval_readings
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ module GreenButtonData
2
+ module Parser
3
+ class IntervalReading
4
+ include SAXMachine
5
+
6
+ element :cost, class: Integer
7
+ element :timePeriod, class: Interval, as: :time_period
8
+ element :value, class: Integer
9
+ element :consumptionTier, class: Integer, as: :consumption_tier
10
+ element :tou, class: Integer
11
+ element :cpp, class: Integer
12
+
13
+ # Standard ESPI namespacing
14
+ element :'espi:cost', class: Integer, as: :cost
15
+ element :'espi:timePeriod', class: Interval, as: :time_period
16
+ element :'espi:value', class: Integer, as: :value
17
+ element :'espi:consumptionTier', class: Integer, as: :consumption_tier
18
+ element :'espi:tou', class: Integer, as: :tou
19
+ element :'espi:cpp', class: Integer, as: :cpp
20
+
21
+ # Special case for PG&E which uses generic namespacing
22
+ element :'ns0:cost', class: Integer, as: :cost
23
+ element :'ns0:timePeriod', class: Interval, as: :time_period
24
+ element :'ns0:value', class: Integer, as: :value
25
+ element :'ns0:consumptionTier', class: Integer, as: :consumption_tier
26
+ element :'ns0:tou', class: Integer, as: :tou
27
+ element :'ns0:cpp', class: Integer, as: :cpp
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,105 @@
1
+ module GreenButtonData
2
+ module Parser
3
+ class LocalTimeParameters
4
+ include SAXMachine
5
+ include Dst
6
+ include Utilities
7
+
8
+ element :dstStartRule, as: :dst_start_rule do |string|
9
+ string.to_i 16
10
+ end
11
+
12
+ element :dstEndRule, as: :dst_end_rule do |string|
13
+ string.to_i 16
14
+ end
15
+
16
+ def dst_starts_at(year = Time.now.year)
17
+ byte_to_dst_datetime @dst_start_rule, year
18
+ end
19
+
20
+ def dst_ends_at(year = Time.now.year)
21
+ byte_to_dst_datetime @dst_end_rule, year
22
+ end
23
+
24
+ element :dstOffset, class: Integer, as: :dst_offset
25
+ element :tzOffset, class: Integer, as: :tz_offset
26
+
27
+ def total_offset
28
+ @dst_offset + @tz_offset
29
+ end
30
+
31
+ def total_offset_hours
32
+ total_offset / 3600
33
+ end
34
+
35
+ # ESPI Namespacing
36
+ element :'espi:dstStartRule', as: :dst_start_rule
37
+ element :'espi:dstEndRule', as: :dst_end_rule
38
+ element :'espi:dstOffset', class: Integer, as: :dst_offset
39
+ element :'espi:tzOffset', class: Integer, as: :tz_offset
40
+
41
+ # Special case for PG&E which uses generic namespacing
42
+ element :'ns0:dstStartRule', as: :dst_start_rule
43
+ element :'ns0:dstEndRule', as: :dst_end_rule
44
+ element :'ns0:dstOffset', class: Integer, as: :dst_offset
45
+ element :'ns0:tzOffset', class: Integer, as: :tz_offset
46
+
47
+ private
48
+
49
+ def byte_to_dst_datetime(byte, year = Time.now.year)
50
+ # Bits 0 - 11: seconds 0 - 3599
51
+ seconds = byte & BITMASK_SECOND
52
+
53
+ # Bits 12 - 16: hours 0 - 23
54
+ hour = (byte & BITMASK_HOUR) >> BITSHIFT_HOUR
55
+
56
+ # Bits 17 - 19: day of the week; 0 = NA, 1 - 7 (Monday = 1)
57
+ weekday = (byte & BITMASK_DAY_OF_WEEK) >> BITSHIFT_DAY_OF_WEEK
58
+
59
+ # Bits 20 - 24: day of the month; 0 = NA, 1 - 31
60
+ day = (byte & BITMASK_DAY_OF_MONTH) >> BITSHIFT_DAY_OF_MONTH
61
+
62
+ # Bits 25 - 27: DST rule 0 - 7
63
+ dst_rule = (byte & BITMASK_DST_RULE) >> BITSHIFT_DST_RULE
64
+
65
+ # Bits 28 - 31: month 1 - 12
66
+ month = (byte & BITMASK_MONTH) >> BITSHIFT_MONTH
67
+
68
+ # Raise an error unless all the values are in valid range
69
+ seconds.between?(0, 3599) and hour.between?(0, 23) and
70
+ weekday.between?(1, 7) and day.between?(0, 31) and
71
+ dst_rule.between?(0, 7) and month.between?(1, 12) or
72
+ raise RangeError, 'Invalid value range'
73
+
74
+ # In Ruby, Sunday = 0 not 7
75
+ weekday = weekday == 7 ? 0 : weekday
76
+
77
+ # Check the DST rule
78
+ dst_day = if dst_rule == 1
79
+ # Rule 1: DST starts/ends on Day of Week on or after the Day of Month
80
+ day_of_month = DateTime.new year, month, day
81
+ day_offset = if weekday >= day_of_month.wday
82
+ weekday - day_of_month.wday
83
+ else
84
+ 7 + weekday - day_of_month.wday
85
+ end
86
+
87
+ day_of_month + day_offset
88
+ elsif dst_rule.between?(2, 6)
89
+ # Rule 2 - 6: DST starts/ends on Nth Day of Week in given month
90
+ # Nth Day of Week (e.g. third Friday of July)
91
+ nth_weekday_of year, month, weekday, dst_rule - 1
92
+ elsif dst_rule == 7
93
+ # Rule 7: DST starts/ends on last Day of Week in given month
94
+ last_weekday_of year, month, weekday
95
+ else
96
+ # Rule 0: DST starts/ends on the Day of Month
97
+ DateTime.new year, month, day
98
+ end
99
+
100
+ # Add the hour and seconds component to the day
101
+ dst_day + Rational(hour * 3600 + seconds, 86400)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,22 @@
1
+ module GreenButtonData
2
+ module Parser
3
+ class RationalNumber
4
+ include SAXMachine
5
+
6
+ element :numerator, class: Integer
7
+ element :denominator, class: Integer
8
+
9
+ def to_f
10
+ @numerator / @denominator
11
+ end
12
+
13
+ # ESPI Namespacing
14
+ element :'espi:numerator', class: Integer, as: :numerator
15
+ element :'espi:denominator', class: Integer, as: :denominator
16
+
17
+ # Special case for PG&E which uses generic namespacing
18
+ element :'ns0:numerator', class: Integer, as: :numerator
19
+ element :'ns0:denominator', class: Integer, as: :denominator
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,134 @@
1
+ module GreenButtonData
2
+ module Parser
3
+ class ReadingType
4
+ include SAXMachine
5
+ include Enumerations
6
+
7
+ element :accumulationBehaviour, class: Integer,
8
+ as: :accumulation_behaviour
9
+ element :commodity, class: Integer
10
+ element :consumptionTier, class: Integer, as: :consumption_tier
11
+ element :currency, class: Integer
12
+ element :dataQualifier, class: Integer, as: :data_qualifier
13
+ element :defaultQuality, class: Integer, as: :default_quality
14
+ element :flowDirection, class: Integer, as: :flow_direction
15
+ element :intervalLength, class: Integer, as: :interval_length
16
+ element :kind, class: Integer
17
+ element :phase, class: Integer
18
+ element :powerOfTenMultiplier, class: Integer,
19
+ as: :power_of_ten_multiplier
20
+ element :timeAttribute, class: Integer, as: :time_attribute
21
+ element :tou, class: Integer
22
+ element :uom, class: Integer
23
+ element :cpp, class: Integer
24
+ element :interharmonic, class: RationalNumber
25
+ element :measuringPeriod, class: Integer, as: :measuring_period
26
+ element :argument, class: RationalNumber
27
+
28
+ def accumulation_behaviour
29
+ ACCUMULATION[@accumulation_behaviour]
30
+ end
31
+
32
+ def argument
33
+ if @argument && @argument.denominator != 0
34
+ @argument.numerator / @argument.denominator
35
+ end
36
+ end
37
+
38
+ def commodity
39
+ COMMODITY[@commodity]
40
+ end
41
+
42
+ def currency
43
+ CURRENCY[@currency]
44
+ end
45
+
46
+ def data_qualifier
47
+ DATA_QUALIFIER[@data_qualifier]
48
+ end
49
+
50
+ def default_quality
51
+ QUALITY_OF_READING[@default_quality]
52
+ end
53
+
54
+ def flow_direction
55
+ FLOW_DIRECTION[@flow_direction]
56
+ end
57
+
58
+ def interharmonic
59
+ # Prevent blackholes from forming
60
+ if @interharmonic && @interharmonic.denominator != 0
61
+ @interharmonic.numerator / @interharmonic.denominator
62
+ end
63
+ end
64
+
65
+ def kind
66
+ MEASUREMENT[@kind]
67
+ end
68
+
69
+ def measuring_period
70
+ TIME_ATTRIBUTE[@measuring_period]
71
+ end
72
+
73
+ def phase
74
+ PHASE_CODE[@phase]
75
+ end
76
+
77
+ def power_of_ten_multiplier
78
+ 10.0 ** @power_of_ten_multiplier
79
+ end
80
+
81
+ def time_attribute
82
+ TIME_PERIOD_OF_INTEREST[@time_attribute]
83
+ end
84
+
85
+ def uom
86
+ UNIT_SYMBOL[@uom]
87
+ end
88
+
89
+ # ESPI Namespacing
90
+ element :'espi:accumulationBehaviour', class: Integer,
91
+ as: :accumulation_behaviour
92
+ element :'espi:commodity', class: Integer, as: :commodity
93
+ element :'espi:consumptionTier', class: Integer, as: :consumption_tier
94
+ element :'espi:currency', class: Integer, as: :currency
95
+ element :'espi:dataQualifier', class: Integer, as: :data_qualifier
96
+ element :'espi:defaultQuality', class: Integer, as: :default_quality
97
+ element :'espi:flowDirection', class: Integer, as: :flow_direction
98
+ element :'espi:intervalLength', class: Integer, as: :interval_length
99
+ element :'espi:kind', class: Integer, as: :kind
100
+ element :'espi:phase', class: Integer, as: :phase
101
+ element :'espi:powerOfTenMultiplier', class: Integer,
102
+ as: :power_of_ten_multiplier
103
+ element :'espi:timeAttribute', class: Integer, as: :time_attribute
104
+ element :'espi:tou', class: Integer, as: :tou
105
+ element :'espi:uom', class: Integer, as: :uom
106
+ element :'espi:cpp', class: Integer, as: :cpp
107
+ element :'espi:interharmonic', class: RationalNumber, as: :interharmonic
108
+ element :'espi:measuringPeriod', class: Integer, as: :measuring_period
109
+ element :'espi:argument', class: RationalNumber, as: :argument
110
+
111
+ # Special case for PG&E which uses generic namespacing
112
+ element :'ns0:accumulationBehaviour', class: Integer,
113
+ as: :accumulation_behaviour
114
+ element :'ns0:commodity', class: Integer, as: :commodity
115
+ element :'ns0:consumptionTier', class: Integer, as: :consumption_tier
116
+ element :'ns0:currency', class: Integer, as: :currency
117
+ element :'ns0:dataQualifier', class: Integer, as: :data_qualifier
118
+ element :'ns0:defaultQuality', class: Integer, as: :default_quality
119
+ element :'ns0:flowDirection', class: Integer, as: :flow_direction
120
+ element :'ns0:intervalLength', class: Integer, as: :interval_length
121
+ element :'ns0:kind', class: Integer, as: :kind
122
+ element :'ns0:phase', class: Integer, as: :phase
123
+ element :'ns0:powerOfTenMultiplier', class: Integer,
124
+ as: :power_of_ten_multiplier
125
+ element :'ns0:timeAttribute', class: Integer, as: :time_attribute
126
+ element :'ns0:tou', class: Integer, as: :tou
127
+ element :'ns0:uom', class: Integer, as: :uom
128
+ element :'ns0:cpp', class: Integer, as: :cpp
129
+ element :'ns0:interharmonic', class: RationalNumber, as: :interharmonic
130
+ element :'ns0:measuringPeriod', class: Integer, as: :measuring_period
131
+ element :'ns0:argument', class: RationalNumber, as: :argument
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,17 @@
1
+ module GreenButtonData
2
+ module Parser
3
+ class ServiceCategory
4
+ include SAXMachine
5
+
6
+ element :kind, class: Integer do |kind|
7
+ GreenButtonData::Enumerations::SERVICE[kind]
8
+ end
9
+
10
+ # ESPI Namespacing
11
+ element :'espi:kind', class: Integer, as: :kind
12
+
13
+ # Special case for PG&E which uses generic namespacing
14
+ element :'ns0:kind', class: Integer, as: :kind
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module GreenButtonData
2
+ module Parser
3
+ class UsagePoint
4
+ include SAXMachine
5
+ include Enumerations
6
+
7
+ element :kind, class: Integer
8
+
9
+ def service_category
10
+ SERVICE[@kind]
11
+ end
12
+
13
+ # ESPI Namespacing
14
+ element :'espi:kind', class: Integer, as: :kind
15
+
16
+ # Special case for PG&E which uses generic namespacing
17
+ element :'ns0:kind', class: Integer, as: :kind
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,2 @@
1
+ module GreenButtonData::Parser
2
+ end
@@ -0,0 +1,106 @@
1
+ module GreenButtonData
2
+ module Utilities
3
+
4
+ def parse_datetime(string)
5
+ begin
6
+ DateTime.parse(string).utc
7
+ rescue
8
+ warn "Parsing failed for string: #{string.inspect}"
9
+ nil
10
+ end
11
+ end
12
+
13
+ ##
14
+ # Normalizes UNIX +epoch+ ticks to seconds.
15
+ #
16
+ # If the number of digits for +epoch+ are greater than or equal to 15
17
+ # decimal digits, it assumes that the +epoch+ is in microseconds.
18
+ #
19
+ # If the number of digits for +epoch+ are less than 15 but greater than or
20
+ # equal to 13 decimal digits, it assumes that +epoch+ is in milliseconds.
21
+ #
22
+ # Less than 13 digits is assumed to be in seconds.
23
+ #
24
+ # ==== Arguments
25
+ #
26
+ # * +epoch+ - Amount of seconds/milliseconds/microseconds since 1970-01-01
27
+ def normalize_epoch(epoch)
28
+ if epoch.digits >= 15
29
+ epoch / 100000
30
+ elsif epoch.digits >= 13
31
+ epoch / 1000
32
+ else
33
+ epoch
34
+ end
35
+ end
36
+
37
+ ##
38
+ # Retrieves the first Sunday of the month
39
+ #
40
+ # ==== Arguments
41
+ #
42
+ # * +year+ - year
43
+ # * +month+ - month
44
+ def first_sunday_of(year = Time.now.year, month = Time.now.month)
45
+ first_day = DateTime.new(year, month, 1)
46
+ first_weekday = first_day.wday
47
+
48
+ # If today is Sunday, no offset, otherwise, calculate number of days that
49
+ # need to be added before hitting the first Sunday of month
50
+ day_offset = first_weekday == 0 ? 0 : 7 - first_weekday
51
+
52
+ # Return first Monday of the month
53
+ first_day + day_offset
54
+ end
55
+
56
+ ##
57
+ # Returns the Nth weekday in the given month
58
+ #
59
+ # ==== Arguments
60
+ #
61
+ # * +year+ - year
62
+ # * +month+ - month
63
+ # * +weekday+ - day of week; 0 = Sunday, 6 = Saturday
64
+ # * +week+ - Nth week of month
65
+ #
66
+ # ==== Examples
67
+ #
68
+ # To retrieve third Friday of July 2015,
69
+ # nth_weekday_of 2015, 7, 5, 3
70
+ def nth_weekday_of(year = Time.now.year, month = Time.now.month,
71
+ weekday = 0, week = 1)
72
+
73
+ # Day offset needed for Nth day of the week
74
+ first_sunday_of(year, month) + weekday + (week - 1) * 7
75
+ end
76
+
77
+ ##
78
+ # Returns the last weekday in the given month
79
+ #
80
+ # ==== Arguments
81
+ #
82
+ # * +year+ - year
83
+ # * +month+ - month
84
+ # * +weekday+ - day of week; 0 = Sunday, 6 = Saturday
85
+ #
86
+ # ==== Examples
87
+ #
88
+ # To retrieve the last Wednesday of September 2015,
89
+ # last_weekday_of 2015, 9, 3
90
+ def last_weekday_of(year = Time.now.year, month = Time.now.month,
91
+ weekday = 0)
92
+
93
+ # Get the last day of month
94
+ last_day = DateTime.new year, month, -1
95
+ last_weekday = last_day.wday
96
+
97
+ day_offset = if last_weekday >= weekday
98
+ last_weekday - weekday
99
+ else
100
+ 7 + last_weekday - weekday
101
+ end
102
+
103
+ last_day - day_offset
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,3 @@
1
+ module GreenButtonData
2
+ VERSION = '0.1.0'
3
+ end