a0-tzmigration-ruby 0.1.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|