a0-tzmigration-ruby 0.1.1 → 1.0.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +50 -6
- data/docs/index.md +6 -0
- data/lib/a0/tzmigration.rb +1 -1
- data/lib/a0/tzmigration/tzversion.rb +37 -72
- data/lib/a0/tzmigration/util.rb +94 -0
- data/lib/a0/tzmigration/version.rb +1 -1
- metadata +3 -3
- data/lib/a0/tzmigration/range_list.rb +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5315ca942000e3f71a8f3e37927f20b2eec0ec7a29dbd8ab71808abe76d63b24
|
4
|
+
data.tar.gz: 8a0c381804c482cf2c04b67ad5db8f96c6ec8b71c15d8052e8a9f002f45c9806
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c64c31ad265828ed9ca784856d8261e02a24c1c60f8e0538cd2ad6dfe38925108af6a5073378148a13a43ec02a95220eb2eb0e607b70a117b020b6d4a5dc6176
|
7
|
+
data.tar.gz: 597161ffde5f4d510ada563e354d4498458123022b9350db906482beb669de0f06efc9d8776223f87ad332f5e0b9c9dee13b73e5d359dd02d43ba40a6e051802
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
# A0::TZMigration
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
This gem provides utilities to help with migrations between timezone changes. Please check the official webpage for documentation:
|
4
|
+
[https://a0.github.io/a0-tzmigration/](https://a0.github.io/a0-tzmigration/)
|
6
5
|
|
7
6
|
## Installation
|
8
7
|
|
9
8
|
Add this line to your application's Gemfile:
|
10
9
|
|
11
10
|
```ruby
|
12
|
-
gem 'a0-tzmigration'
|
11
|
+
gem 'a0-tzmigration-ruby'
|
13
12
|
```
|
14
13
|
|
15
14
|
And then execute:
|
@@ -18,11 +17,56 @@ And then execute:
|
|
18
17
|
|
19
18
|
Or install it yourself as:
|
20
19
|
|
21
|
-
$ gem install a0-tzmigration
|
20
|
+
$ gem install a0-tzmigration-ruby
|
22
21
|
|
23
22
|
## Usage
|
24
23
|
|
25
|
-
|
24
|
+
```ruby
|
25
|
+
require 'a0-tzmigration-ruby'
|
26
|
+
|
27
|
+
# calculate changes from America/Santiago 2015a to America/Punta_Arenas 2017a
|
28
|
+
version_a = A0::TZMigration::TZVersion.new('America/Santiago', '2015a')
|
29
|
+
version_b = A0::TZMigration::TZVersion.new('America/Punta_Arenas', '2017a')
|
30
|
+
|
31
|
+
version_a.changes(version_b)
|
32
|
+
# =>
|
33
|
+
[{:ini_str=>"-∞", :fin_str=>"1890-01-01 04:43:40 UTC", :off_str=>"-00:00:54", :ini=>-Infinity, :fin=>-2524504580, :off=>-54},
|
34
|
+
{:ini_str=>"1910-01-01 04:42:46 UTC", :fin_str=>"1910-01-10 04:42:46 UTC", :off_str=>"+00:17:14", :ini=>-1893439034, :fin=>-1892661434, :off=>1034},
|
35
|
+
{:ini_str=>"1918-09-01 04:42:46 UTC", :fin_str=>"1918-09-10 04:42:46 UTC", :off_str=>"-00:42:46", :ini=>-1619983034, :fin=>-1619205434, :off=>-2566},
|
36
|
+
{:ini_str=>"1946-09-01 03:00:00 UTC", :fin_str=>"1947-04-01 04:00:00 UTC", :off_str=>"+01:00:00", :ini=>-736376400, :fin=>-718056000, :off=>3600},
|
37
|
+
{:ini_str=>"1947-05-22 04:00:00 UTC", :fin_str=>"1947-05-22 05:00:00 UTC", :off_str=>"+01:00:00", :ini=>-713649600, :fin=>-713646000, :off=>3600},
|
38
|
+
{:ini_str=>"1988-10-02 04:00:00 UTC", :fin_str=>"1988-10-09 04:00:00 UTC", :off_str=>"-01:00:00", :ini=>591768000, :fin=>592372800, :off=>-3600},
|
39
|
+
{:ini_str=>"1990-03-11 03:00:00 UTC", :fin_str=>"1990-03-18 03:00:00 UTC", :off_str=>"-01:00:00", :ini=>637124400, :fin=>637729200, :off=>-3600},
|
40
|
+
{:ini_str=>"2016-05-15 03:00:00 UTC", :fin_str=>"2016-08-14 04:00:00 UTC", :off_str=>"-01:00:00", :ini=>1463281200, :fin=>1471147200, :off=>-3600}]
|
41
|
+
|
42
|
+
|
43
|
+
# get current known versions in the repository
|
44
|
+
A0::TZMigration::TZVersion.versions
|
45
|
+
# =>
|
46
|
+
{ "2013c" =>
|
47
|
+
{ "released_at" => "2013-04-19 16:17:40 -0700",
|
48
|
+
"timezones" => [
|
49
|
+
"Africa/Abidjan",
|
50
|
+
"Africa/Accra",
|
51
|
+
"Africa/Addis_Ababa",
|
52
|
+
"Africa/Algiers",
|
53
|
+
"Africa/Asmara",
|
54
|
+
…
|
55
|
+
|
56
|
+
|
57
|
+
# get current known timezones in the repository
|
58
|
+
A0::TZMigration::TZVersion.timezones
|
59
|
+
# =>
|
60
|
+
{ "Africa/Abidjan" =>
|
61
|
+
{ "versions" => [
|
62
|
+
"2013c",
|
63
|
+
"2013d",
|
64
|
+
"2013e",
|
65
|
+
"2013f",
|
66
|
+
"2013g",
|
67
|
+
…
|
68
|
+
|
69
|
+
```
|
26
70
|
|
27
71
|
## Development
|
28
72
|
|
data/docs/index.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# Ruby API
|
2
|
+
|
3
|
+
API usage and documentation for the ruby gem and others can be found at the official webpage:
|
4
|
+
[https://a0.github.io/a0-tzmigration/](https://a0.github.io/a0-tzmigration/)
|
5
|
+
|
6
|
+
|
1
7
|
# JSON data files
|
2
8
|
|
3
9
|
All info is publicly available at the following URL:
|
data/lib/a0/tzmigration.rb
CHANGED
@@ -1,74 +1,61 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rest-client'
|
4
|
-
|
5
3
|
module A0
|
6
4
|
module TZMigration
|
7
5
|
class TZVersion
|
8
|
-
attr_reader :
|
6
|
+
attr_reader :name, :version
|
9
7
|
|
10
8
|
def initialize(name, version)
|
11
9
|
@name = name
|
12
10
|
@version = version
|
13
|
-
|
14
|
-
transitions
|
15
11
|
end
|
16
12
|
|
17
|
-
def
|
18
|
-
|
13
|
+
def data
|
14
|
+
return @data if defined? @data
|
19
15
|
|
20
|
-
|
16
|
+
@data = Util.load_from_network_or_file("timezones/#{name}.json")
|
21
17
|
end
|
22
18
|
|
23
|
-
def
|
24
|
-
|
25
|
-
file = File.join(conf, path)
|
26
|
-
|
27
|
-
raise "File #{path} not found at #{conf}" unless File.exist? file
|
28
|
-
|
29
|
-
JSON.parse File.read(file)
|
30
|
-
end
|
19
|
+
def version_data
|
20
|
+
return @version_data if defined? @version_data
|
31
21
|
|
32
|
-
|
33
|
-
load_from_network(path)
|
34
|
-
rescue StandardError => error
|
35
|
-
warn "Unable to fetch from network, using local files (error was: #{error})"
|
36
|
-
load_from_file(path)
|
37
|
-
end
|
22
|
+
raise "Version #{@version} not found for #{@name}." unless (@version_data = data.dig('versions', @version))
|
38
23
|
|
39
|
-
|
40
|
-
|
24
|
+
if @version_data['alias']
|
25
|
+
@link = TZVersion.new(@version_data['alias'], @version)
|
26
|
+
@version_data = @link.version_data
|
27
|
+
end
|
41
28
|
|
42
|
-
@
|
29
|
+
@version_data
|
43
30
|
end
|
44
31
|
|
45
32
|
def released_at
|
46
33
|
return @released_at if defined? @released_at
|
47
34
|
|
48
|
-
@released_at =
|
35
|
+
@released_at = version_data['released_at']
|
49
36
|
end
|
50
37
|
|
51
|
-
def
|
52
|
-
return @
|
53
|
-
|
54
|
-
raise "Version #{@version} not found" unless (@version_data = data.dig('versions', @version))
|
38
|
+
def transitions
|
39
|
+
return @transitions if defined? @transitions
|
55
40
|
|
56
|
-
@version_data
|
41
|
+
@transitions = version_data['transitions']
|
57
42
|
end
|
58
43
|
|
59
|
-
def
|
60
|
-
return @
|
44
|
+
def transition_ranges # rubocop:disable Metrics/AbcSize
|
45
|
+
return @transition_ranges if defined? @transition_ranges
|
46
|
+
|
47
|
+
ini = -Float::INFINITY
|
48
|
+
fin = +Float::INFINITY
|
61
49
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
@transitions.each do |transition|
|
67
|
-
transition['utc_time'] = Time.parse(transition['utc_time'])
|
68
|
-
end
|
50
|
+
return @transition_ranges = [Util.range_item(ini, fin, 0)] if transitions.empty?
|
51
|
+
|
52
|
+
@transition_ranges = transitions.map do |transition|
|
53
|
+
Util.range_item(ini, (ini = transition['utc_timestamp']), transition['utc_prev_offset'])
|
69
54
|
end
|
70
55
|
|
71
|
-
@transitions
|
56
|
+
@transition_ranges << Util.range_item(@transition_ranges.last[:fin], fin, transitions.last['utc_offset'])
|
57
|
+
|
58
|
+
@transition_ranges
|
72
59
|
end
|
73
60
|
|
74
61
|
def timestamps
|
@@ -83,50 +70,28 @@ module A0
|
|
83
70
|
@timestamps
|
84
71
|
end
|
85
72
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
ini = -Float::INFINITY
|
90
|
-
fin = +Float::INFINITY
|
91
|
-
|
92
|
-
@timezone_ranges = transitions.map do |transition|
|
93
|
-
{ ini: ini, fin: (ini = transition['utc_timestamp']), off: transition['utc_prev_offset'] }
|
94
|
-
end
|
95
|
-
|
96
|
-
@timezone_ranges << { ini: @timezone_ranges.last[:fin], fin: fin, off: transitions.last['utc_offset'] } unless @timezone_ranges.empty?
|
97
|
-
|
98
|
-
@timezone_ranges
|
99
|
-
end
|
100
|
-
|
101
|
-
def timezone_ranges_timed
|
102
|
-
A0::TZMigration.timestamp_range_list!(Marshal.dump(timezone_ranges))
|
103
|
-
end
|
104
|
-
|
105
|
-
def delta_range_list(other) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
106
|
-
raise "No transitions for self #{@name}/#{@version}" if transitions.empty?
|
107
|
-
raise "No transitions for other #{name}/#{version}" if other.transitions.empty?
|
108
|
-
|
109
|
-
timestamp_list = (timestamps + other.timestamps).uniq.sort
|
73
|
+
def changes(other) # rubocop:disable Metrics/AbcSize
|
74
|
+
timestamp_list = (timestamps + other.timestamps).sort.uniq
|
110
75
|
|
111
|
-
list_a =
|
112
|
-
list_b =
|
76
|
+
list_a = Util.split_ranges(transition_ranges, timestamp_list)
|
77
|
+
list_b = Util.split_ranges(other.transition_ranges, timestamp_list)
|
113
78
|
|
114
|
-
|
79
|
+
changes = []
|
115
80
|
list_a.each_with_index do |range_a, index|
|
116
81
|
range_b = list_b[index]
|
117
82
|
|
118
|
-
|
83
|
+
changes << Util.range_item(range_a[:ini], range_a[:fin], range_b[:off] - range_a[:off]) if range_a[:off] != range_b[:off]
|
119
84
|
end
|
120
85
|
|
121
|
-
|
86
|
+
Util.compact_ranges!(changes)
|
122
87
|
end
|
123
88
|
|
124
89
|
def self.versions
|
125
|
-
@versions = load_from_network_or_file('versions/00-index.json')
|
90
|
+
@versions = Util.load_from_network_or_file('versions/00-index.json')['versions']
|
126
91
|
end
|
127
92
|
|
128
93
|
def self.timezones
|
129
|
-
@timezones = load_from_network_or_file('timezones/00-index.json')
|
94
|
+
@timezones = Util.load_from_network_or_file('timezones/00-index.json')['timezones']
|
130
95
|
end
|
131
96
|
end
|
132
97
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rest-client'
|
4
|
+
|
5
|
+
module A0
|
6
|
+
module TZMigration
|
7
|
+
module Util
|
8
|
+
def self.compact_ranges!(range_list) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
9
|
+
index = 0
|
10
|
+
|
11
|
+
while index < range_list.count
|
12
|
+
curr_range = range_list[index]
|
13
|
+
next_range = range_list[index + 1]
|
14
|
+
|
15
|
+
if next_range && curr_range[:fin] == next_range[:ini] && curr_range[:off] == next_range[:off]
|
16
|
+
curr_range[:fin] = next_range[:fin]
|
17
|
+
curr_range[:fin_str] = next_range[:fin_str]
|
18
|
+
range_list.delete_at(index + 1)
|
19
|
+
else
|
20
|
+
index += 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
range_list
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.offset_to_str(offset)
|
28
|
+
str = Time.at(offset.abs).utc.strftime('%H:%M:%S')
|
29
|
+
sig = offset.negative? ? '-' : '+'
|
30
|
+
|
31
|
+
"#{sig}#{str}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.timestamp_to_str(timestamp)
|
35
|
+
if timestamp == -Float::INFINITY
|
36
|
+
'-∞'
|
37
|
+
elsif timestamp == +Float::INFINITY
|
38
|
+
'∞'
|
39
|
+
else
|
40
|
+
Time.at(timestamp).utc.to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.range_item(ini, fin, off)
|
45
|
+
{ ini_str: timestamp_to_str(ini), fin_str: timestamp_to_str(fin), off_str: offset_to_str(off), ini: ini, fin: fin, off: off }
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.next_index(index, range_list, timestamp)
|
49
|
+
index += 1 while range_list[index + 1] && range_list[index][:ini] < timestamp && range_list[index][:fin] <= timestamp
|
50
|
+
|
51
|
+
index
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.split_ranges(range_list, timestamps) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
55
|
+
range_list = Marshal.load(Marshal.dump(range_list))
|
56
|
+
index = 0
|
57
|
+
|
58
|
+
timestamps.each do |timestamp|
|
59
|
+
index = next_index(index, range_list, timestamp)
|
60
|
+
range = range_list[index]
|
61
|
+
|
62
|
+
if index < range_list.count && timestamp > range[:ini] && timestamp < range[:fin]
|
63
|
+
range_list.insert index + 1, range.merge(ini: timestamp)
|
64
|
+
range_list[index][:fin] = timestamp
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
range_list
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.load_from_network(path)
|
72
|
+
url = "#{A0::TZMigration.config.base_url}/#{path}"
|
73
|
+
|
74
|
+
JSON.parse RestClient.get(url)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.load_from_file(path)
|
78
|
+
conf = A0::TZMigration.config.data_dir
|
79
|
+
file = File.join(conf, path)
|
80
|
+
|
81
|
+
raise "File #{path} not found at #{conf}" unless File.exist? file
|
82
|
+
|
83
|
+
JSON.parse File.read(file)
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.load_from_network_or_file(path)
|
87
|
+
load_from_network(path)
|
88
|
+
rescue StandardError => error
|
89
|
+
warn "Unable to fetch from network, using local files (error was: #{error})"
|
90
|
+
load_from_file(path)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: a0-tzmigration-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aldrin Martoq
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -827,8 +827,8 @@ files:
|
|
827
827
|
- lib/a0/tzmigration/data_generator/git.rb
|
828
828
|
- lib/a0/tzmigration/data_generator/process.rb
|
829
829
|
- lib/a0/tzmigration/data_generator/save.rb
|
830
|
-
- lib/a0/tzmigration/range_list.rb
|
831
830
|
- lib/a0/tzmigration/tzversion.rb
|
831
|
+
- lib/a0/tzmigration/util.rb
|
832
832
|
- lib/a0/tzmigration/version.rb
|
833
833
|
homepage: http://github.com/a0/a0-tzmigration-ruby
|
834
834
|
licenses:
|
@@ -1,61 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module A0
|
4
|
-
module TZMigration
|
5
|
-
def self.compact_range_list!(range_list) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
6
|
-
index = 0
|
7
|
-
|
8
|
-
while index < range_list.count
|
9
|
-
curr_range = range_list[index]
|
10
|
-
next_range = range_list[index + 1]
|
11
|
-
|
12
|
-
if next_range && curr_range[:fin] == next_range[:ini] && curr_range[:off] == next_range[:off]
|
13
|
-
curr_range[:fin] = next_range[:fin]
|
14
|
-
range_list.delete_at(index + 1)
|
15
|
-
else
|
16
|
-
index += 1
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
range_list
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.next_index(index, range_list, timestamp)
|
24
|
-
index += 1 while range_list[index + 1] && range_list[index][:ini] < timestamp && range_list[index][:fin] <= timestamp
|
25
|
-
|
26
|
-
index
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.timestamp_to_utc(timestamp)
|
30
|
-
Time.at(timestamp).utc if timestamp && timestamp != Float::INFINITY && timestamp != -Float::INFINITY
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.timestamp_range_list!(range_list)
|
34
|
-
range_list.each do |range|
|
35
|
-
range[:utc_ini] = timestamp_to_utc(range[:ini])
|
36
|
-
range[:utc_fin] = timestamp_to_utc(range[:fin])
|
37
|
-
end
|
38
|
-
|
39
|
-
range_list
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.split_range_list!(range_list, timestamps) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
43
|
-
index = next_index(0, range_list, timestamps.first)
|
44
|
-
|
45
|
-
timestamps.each_with_index do |timestamp, timestamp_index|
|
46
|
-
range = range_list[index]
|
47
|
-
|
48
|
-
if timestamp > range[:ini] && timestamp < range[:fin] && index < range_list.count
|
49
|
-
range_list.insert index + 1, range.merge(ini: timestamp)
|
50
|
-
range_list[index][:fin] = timestamp
|
51
|
-
end
|
52
|
-
|
53
|
-
next_timestamp = timestamps[timestamp_index + 1]
|
54
|
-
|
55
|
-
index = next_index(index, range_list, next_timestamp) if next_timestamp
|
56
|
-
end
|
57
|
-
|
58
|
-
range_list
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|