MetricCalendar 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 +7 -0
- data/lib/metric_calendar.rb +146 -0
- metadata +45 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7985b53d95778cdfde8aced371201714c3b58366bb6e34290354dc56f2d8280b
|
|
4
|
+
data.tar.gz: 79ecf8c72a6cb27feb886a309b6e13d28bd96bf275d8f82256d803c794376c73
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1b3c02405c6e619c9213a10188946afac7cc6863ffae7dc198ca6363ba4d99abfea543312ed9fc882ee2cd9c81500530ed031479b4133132c88f8b6c9de9ae1d
|
|
7
|
+
data.tar.gz: 79ad2eb397818ced0299c1cd475b7373dcdfe6d11ffff486a82af52e143fa03134040f32b2fc06ea78163f892b26db433ec73fa7b0f5c41ec93840c84fb9fcff
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
require 'date'
|
|
2
|
+
|
|
3
|
+
module MetricCalendar
|
|
4
|
+
DAY_NAMES = %w[Primday Duoday Triday Quadday Quintday Hexday Septday Octday Novday Decday].freeze
|
|
5
|
+
MONTH_NAMES = %w[Unil Duil Tril Quadril Quintil Sextil Septil Octil Novil Decil Undecil Duodecil].freeze
|
|
6
|
+
TURNING_DAY_NAMES = %w[Vigil Balance Dawn].freeze
|
|
7
|
+
YULE_DAY_NAMES = ['Yule Eve', 'Midwinter', 'Kindling'].freeze
|
|
8
|
+
|
|
9
|
+
# Struct for a Metric Calendar date. Fields:
|
|
10
|
+
# year - Metric year (Year 0 = spring equinox 1970)
|
|
11
|
+
# month - 1-12, 0 for Turning/Yule
|
|
12
|
+
# month_name - e.g. "Unil", "" for Turning/Yule
|
|
13
|
+
# day - 1-30, 0 for Turning/Yule
|
|
14
|
+
# week_day - 1-10, 0 for Turning/Yule
|
|
15
|
+
# day_name - e.g. "Primday", "" for Turning/Yule
|
|
16
|
+
# week - 1-36, 0 for Turning/Yule
|
|
17
|
+
# season_index - 0-3, -1 for Turning/Yule
|
|
18
|
+
# is_leap_year - true if this metric year has 3 Yule days
|
|
19
|
+
# is_turning - true during The Turning (3 days at spring equinox)
|
|
20
|
+
# is_yule - true during Yule
|
|
21
|
+
# is_midsummer - true on Quadril 1 (summer solstice)
|
|
22
|
+
# is_spiral - true on Quintil 18 (golden angle day)
|
|
23
|
+
# is_rest - true on days 8-10 of any 10-day week
|
|
24
|
+
# special_day - "Vigil", "Balance", "Dawn", "Yule Eve", "Midwinter", "Kindling", or ""
|
|
25
|
+
MetricDate = Struct.new(
|
|
26
|
+
:year, :month, :month_name, :day, :week_day, :day_name,
|
|
27
|
+
:week, :season_index, :is_leap_year, :is_turning, :is_yule,
|
|
28
|
+
:is_midsummer, :is_spiral, :is_rest, :special_day,
|
|
29
|
+
keyword_init: true
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def self.leap_year?(gregorian_year)
|
|
33
|
+
gregorian_year % 4 == 0 && (gregorian_year % 100 != 0 || gregorian_year % 400 == 0)
|
|
34
|
+
end
|
|
35
|
+
private_class_method :leap_year?
|
|
36
|
+
|
|
37
|
+
# Convert a Gregorian Date to a MetricDate.
|
|
38
|
+
# @param date [Date] the Gregorian date to convert
|
|
39
|
+
# @return [MetricDate]
|
|
40
|
+
def self.gregorian_to_metric(date)
|
|
41
|
+
year = date.year
|
|
42
|
+
equinox = Date.new(year, 3, 20)
|
|
43
|
+
days_from_equinox = (date - equinox).to_i
|
|
44
|
+
|
|
45
|
+
if days_from_equinox >= 0
|
|
46
|
+
metric_year = year - 1970
|
|
47
|
+
day_of_year = days_from_equinox + 1
|
|
48
|
+
else
|
|
49
|
+
metric_year = year - 1 - 1970
|
|
50
|
+
prev_equinox = Date.new(year - 1, 3, 20)
|
|
51
|
+
day_of_year = (date - prev_equinox).to_i + 1
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
leap = leap_year?(metric_year + 1971)
|
|
55
|
+
yule_day_count = leap ? 3 : 2
|
|
56
|
+
|
|
57
|
+
base = {
|
|
58
|
+
year: metric_year, month: 0, month_name: '', day: 0, week_day: 0,
|
|
59
|
+
day_name: '', week: 0, season_index: -1,
|
|
60
|
+
is_leap_year: leap, is_turning: false, is_yule: false,
|
|
61
|
+
is_midsummer: false, is_spiral: false, is_rest: false, special_day: ''
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# The Turning (days 1-3)
|
|
65
|
+
if day_of_year <= 3
|
|
66
|
+
return MetricDate.new(**base.merge(is_turning: true, special_day: TURNING_DAY_NAMES[day_of_year - 1]))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
adjusted = day_of_year - 3
|
|
70
|
+
|
|
71
|
+
if adjusted <= 270
|
|
72
|
+
m = (adjusted - 1) / 30 + 1
|
|
73
|
+
d = (adjusted - 1) % 30 + 1
|
|
74
|
+
elsif adjusted <= 270 + yule_day_count
|
|
75
|
+
return MetricDate.new(**base.merge(is_yule: true, special_day: YULE_DAY_NAMES[adjusted - 271]))
|
|
76
|
+
else
|
|
77
|
+
post_yule = adjusted - 270 - yule_day_count
|
|
78
|
+
m = 9 + (post_yule - 1) / 30 + 1
|
|
79
|
+
d = (post_yule - 1) % 30 + 1
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
week_day = (d - 1) % 10 + 1
|
|
83
|
+
week = (m - 1) * 3 + (d - 1) / 10 + 1
|
|
84
|
+
|
|
85
|
+
MetricDate.new(
|
|
86
|
+
year: metric_year,
|
|
87
|
+
month: m, month_name: MONTH_NAMES[m - 1],
|
|
88
|
+
day: d, week_day: week_day, day_name: DAY_NAMES[week_day - 1],
|
|
89
|
+
week: week, season_index: (m - 1) / 3,
|
|
90
|
+
is_leap_year: leap,
|
|
91
|
+
is_turning: false, is_yule: false,
|
|
92
|
+
is_midsummer: (m == 4 && d == 1),
|
|
93
|
+
is_spiral: (m == 5 && d == 18),
|
|
94
|
+
is_rest: week_day >= 8,
|
|
95
|
+
special_day: ''
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Convert a Metric Calendar date back to a Gregorian Date.
|
|
100
|
+
# @param year [Integer] Metric year
|
|
101
|
+
# @param period_type [String] "turning", "month", or "yule"
|
|
102
|
+
# @param period_value [Integer] 0-indexed turning/yule (0-2), or 1-12 for month
|
|
103
|
+
# @param day_of_month [Integer] 1-30, used only when period_type is "month"
|
|
104
|
+
# @return [Date]
|
|
105
|
+
def self.metric_to_gregorian(year, period_type, period_value, day_of_month = 1)
|
|
106
|
+
equinox_year = year + 1970
|
|
107
|
+
leap = leap_year?(year + 1971)
|
|
108
|
+
yule_day_count = leap ? 3 : 2
|
|
109
|
+
|
|
110
|
+
offset = case period_type
|
|
111
|
+
when 'turning'
|
|
112
|
+
raise ArgumentError, 'turning period_value must be 0-2' unless (0..2).include?(period_value)
|
|
113
|
+
period_value
|
|
114
|
+
when 'month'
|
|
115
|
+
m, d = period_value, day_of_month
|
|
116
|
+
raise ArgumentError, 'month must be 1-12' unless (1..12).include?(m)
|
|
117
|
+
raise ArgumentError, 'day must be 1-30' unless (1..30).include?(d)
|
|
118
|
+
if m <= 9
|
|
119
|
+
3 + (m - 1) * 30 + (d - 1)
|
|
120
|
+
else
|
|
121
|
+
3 + 270 + yule_day_count + (m - 10) * 30 + (d - 1)
|
|
122
|
+
end
|
|
123
|
+
when 'yule'
|
|
124
|
+
raise ArgumentError, 'Kindling only occurs in leap years' if period_value == 2 && !leap
|
|
125
|
+
raise ArgumentError, 'yule period_value must be 0-2' unless (0..2).include?(period_value)
|
|
126
|
+
3 + 270 + period_value
|
|
127
|
+
else
|
|
128
|
+
raise ArgumentError, "Unknown period_type: #{period_type.inspect}"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
Date.new(equinox_year, 3, 20) + offset
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Returns true if the given date is a rest day (days 8-10 of any 10-day week).
|
|
135
|
+
# @param date [Date]
|
|
136
|
+
# @return [Boolean]
|
|
137
|
+
def self.is_rest_day(date)
|
|
138
|
+
gregorian_to_metric(date).is_rest
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Returns the current Metric Calendar date.
|
|
142
|
+
# @return [MetricDate]
|
|
143
|
+
def self.today
|
|
144
|
+
gregorian_to_metric(Date.today)
|
|
145
|
+
end
|
|
146
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: MetricCalendar
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Alex Rabarts
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-16 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: A Ruby library for working with the Metric Calendar — a rational decimal
|
|
14
|
+
calendar system with 10-day weeks and 12 months of 30 days.
|
|
15
|
+
email: []
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- lib/metric_calendar.rb
|
|
21
|
+
homepage: https://metricweek.com
|
|
22
|
+
licenses:
|
|
23
|
+
- MIT
|
|
24
|
+
metadata:
|
|
25
|
+
source_code_uri: https://github.com/alexrabarts/metric-calendar
|
|
26
|
+
post_install_message:
|
|
27
|
+
rdoc_options: []
|
|
28
|
+
require_paths:
|
|
29
|
+
- lib
|
|
30
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
- - ">="
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: '2.5'
|
|
35
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
requirements: []
|
|
41
|
+
rubygems_version: 3.0.3.1
|
|
42
|
+
signing_key:
|
|
43
|
+
specification_version: 4
|
|
44
|
+
summary: Metric Calendar date conversion
|
|
45
|
+
test_files: []
|