MetricCalendar 1.0.0 → 1.0.1

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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/metric_calendar.rb +101 -22
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7985b53d95778cdfde8aced371201714c3b58366bb6e34290354dc56f2d8280b
4
- data.tar.gz: 79ecf8c72a6cb27feb886a309b6e13d28bd96bf275d8f82256d803c794376c73
3
+ metadata.gz: b09d0f43797e81cc342c4391c02e1ecb7060819b6c5ad9e756cf3827f9425590
4
+ data.tar.gz: dbf242e1f7c9a6c1ab7b56417bf0e1a2dfc66576e72944c7ee19235752e6e528
5
5
  SHA512:
6
- metadata.gz: 1b3c02405c6e619c9213a10188946afac7cc6863ffae7dc198ca6363ba4d99abfea543312ed9fc882ee2cd9c81500530ed031479b4133132c88f8b6c9de9ae1d
7
- data.tar.gz: 79ad2eb397818ced0299c1cd475b7373dcdfe6d11ffff486a82af52e143fa03134040f32b2fc06ea78163f892b26db433ec73fa7b0f5c41ec93840c84fb9fcff
6
+ metadata.gz: 8b6d5edc95adca7e75c6b5900a07f8d374f80e33e40512171e312c504bdbae3333f3f993af51cd3907943008e0ab503d0e610a3040f7a5a74cc1b8d5bb7abb22
7
+ data.tar.gz: 4e9840cf9288135efeb5af5b32513de537471fe16182aaf318dc14cff8bfa31a4b1fb828e2dce8f120dae358f144d93315ec443c9a4e7d64918da54215a09225
@@ -3,29 +3,41 @@ require 'date'
3
3
  module MetricCalendar
4
4
  DAY_NAMES = %w[Primday Duoday Triday Quadday Quintday Hexday Septday Octday Novday Decday].freeze
5
5
  MONTH_NAMES = %w[Unil Duil Tril Quadril Quintil Sextil Septil Octil Novil Decil Undecil Duodecil].freeze
6
+ SEASON_NAMES = %w[Rising Flourishing Gathering Stillness].freeze
6
7
  TURNING_DAY_NAMES = %w[Vigil Balance Dawn].freeze
7
8
  YULE_DAY_NAMES = ['Yule Eve', 'Midwinter', 'Kindling'].freeze
8
9
 
10
+ FORMAT_RE = /MMM|MM|M|DD|D|WW|W|Y|S/.freeze
11
+
9
12
  # 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 ""
13
+ # year - Metric year (Year 0 = spring equinox 1970)
14
+ # month - 1-12, 0 for Turning/Yule
15
+ # month_name - e.g. "Unil", "" for Turning/Yule
16
+ # day - 1-30, 0 for Turning/Yule
17
+ # week_day - 1-10, 0 for Turning/Yule
18
+ # day_name - e.g. "Primday", "" for Turning/Yule
19
+ # week - 1-36, 0 for Turning/Yule
20
+ # season_index - 0-3, -1 for Turning/Yule
21
+ # is_leap_year - true if this metric year has 3 Yule days
22
+ # is_turning - true during The Turning (3 days at spring equinox)
23
+ # is_yule - true during Yule
24
+ # is_midsummer - true on Quadril 1 (summer solstice)
25
+ # is_sextant - true on Duil 30
26
+ # is_trine - true on Quadril 30
27
+ # is_spiral - true on Quintil 18 (golden angle day)
28
+ # is_convergence - true on Quintil 24
29
+ # is_meridian - true on Sextil 30
30
+ # is_mask - true on Octil 13
31
+ # is_harmony - true on Octil 30
32
+ # is_rest - true on days 8-10 of any 10-day week
33
+ # special_day - "Vigil", "Balance", "Dawn", "Yule Eve", "Midwinter", "Kindling", or ""
34
+ # observance - name of observance, or "" if none
25
35
  MetricDate = Struct.new(
26
36
  :year, :month, :month_name, :day, :week_day, :day_name,
27
37
  :week, :season_index, :is_leap_year, :is_turning, :is_yule,
28
- :is_midsummer, :is_spiral, :is_rest, :special_day,
38
+ :is_midsummer, :is_sextant, :is_trine, :is_spiral,
39
+ :is_convergence, :is_meridian, :is_mask, :is_harmony,
40
+ :is_rest, :special_day, :observance,
29
41
  keyword_init: true
30
42
  )
31
43
 
@@ -58,12 +70,15 @@ module MetricCalendar
58
70
  year: metric_year, month: 0, month_name: '', day: 0, week_day: 0,
59
71
  day_name: '', week: 0, season_index: -1,
60
72
  is_leap_year: leap, is_turning: false, is_yule: false,
61
- is_midsummer: false, is_spiral: false, is_rest: false, special_day: ''
73
+ is_midsummer: false, is_sextant: false, is_trine: false, is_spiral: false,
74
+ is_convergence: false, is_meridian: false, is_mask: false, is_harmony: false,
75
+ is_rest: false, special_day: '', observance: ''
62
76
  }
63
77
 
64
78
  # The Turning (days 1-3)
65
79
  if day_of_year <= 3
66
- return MetricDate.new(**base.merge(is_turning: true, special_day: TURNING_DAY_NAMES[day_of_year - 1]))
80
+ name = TURNING_DAY_NAMES[day_of_year - 1]
81
+ return MetricDate.new(**base.merge(is_turning: true, special_day: name, observance: name))
67
82
  end
68
83
 
69
84
  adjusted = day_of_year - 3
@@ -72,7 +87,8 @@ module MetricCalendar
72
87
  m = (adjusted - 1) / 30 + 1
73
88
  d = (adjusted - 1) % 30 + 1
74
89
  elsif adjusted <= 270 + yule_day_count
75
- return MetricDate.new(**base.merge(is_yule: true, special_day: YULE_DAY_NAMES[adjusted - 271]))
90
+ name = YULE_DAY_NAMES[adjusted - 271]
91
+ return MetricDate.new(**base.merge(is_yule: true, special_day: name, observance: name))
76
92
  else
77
93
  post_yule = adjusted - 270 - yule_day_count
78
94
  m = 9 + (post_yule - 1) / 30 + 1
@@ -82,6 +98,26 @@ module MetricCalendar
82
98
  week_day = (d - 1) % 10 + 1
83
99
  week = (m - 1) * 3 + (d - 1) / 10 + 1
84
100
 
101
+ is_midsummer = m == 4 && d == 1
102
+ is_sextant = m == 2 && d == 30
103
+ is_trine = m == 4 && d == 30
104
+ is_spiral = m == 5 && d == 18
105
+ is_convergence = m == 5 && d == 24
106
+ is_meridian = m == 6 && d == 30
107
+ is_mask = m == 8 && d == 13
108
+ is_harmony = m == 8 && d == 30
109
+
110
+ observance = if is_midsummer then 'Midsummer'
111
+ elsif is_sextant then 'The Sextant'
112
+ elsif is_trine then 'The Trine'
113
+ elsif is_spiral then 'The Spiral'
114
+ elsif is_convergence then 'Convergence'
115
+ elsif is_meridian then 'The Meridian'
116
+ elsif is_mask then 'The Mask'
117
+ elsif is_harmony then 'Harmony'
118
+ else ''
119
+ end
120
+
85
121
  MetricDate.new(
86
122
  year: metric_year,
87
123
  month: m, month_name: MONTH_NAMES[m - 1],
@@ -89,13 +125,56 @@ module MetricCalendar
89
125
  week: week, season_index: (m - 1) / 3,
90
126
  is_leap_year: leap,
91
127
  is_turning: false, is_yule: false,
92
- is_midsummer: (m == 4 && d == 1),
93
- is_spiral: (m == 5 && d == 18),
128
+ is_midsummer: is_midsummer,
129
+ is_sextant: is_sextant,
130
+ is_trine: is_trine,
131
+ is_spiral: is_spiral,
132
+ is_convergence: is_convergence,
133
+ is_meridian: is_meridian,
134
+ is_mask: is_mask,
135
+ is_harmony: is_harmony,
94
136
  is_rest: week_day >= 8,
95
- special_day: ''
137
+ special_day: '',
138
+ observance: observance
96
139
  )
97
140
  end
98
141
 
142
+ # Format a MetricDate using a pattern string.
143
+ #
144
+ # Tokens:
145
+ # MMM month name (e.g. "Unil")
146
+ # MM month zero-padded (e.g. "01")
147
+ # M month number (e.g. "1")
148
+ # DD day zero-padded (e.g. "04")
149
+ # D day number (e.g. "4")
150
+ # WW weekday name (e.g. "Quintday")
151
+ # W weekday number (e.g. "5")
152
+ # Y year number (e.g. "56")
153
+ # S season name (e.g. "Rising")
154
+ #
155
+ # Example: format(d, "WW, MMM D, Year Y") => "Quintday, Unil 4, Year 56"
156
+ #
157
+ # @param date [MetricDate]
158
+ # @param pattern [String]
159
+ # @return [String]
160
+ def self.format(date, pattern)
161
+ season_name = date.season_index >= 0 && date.season_index <= 3 ? SEASON_NAMES[date.season_index] : ''
162
+ pattern.gsub(FORMAT_RE) do |token|
163
+ case token
164
+ when 'MMM' then date.month_name
165
+ when 'MM' then format('%02d', date.month)
166
+ when 'M' then date.month.to_s
167
+ when 'DD' then format('%02d', date.day)
168
+ when 'D' then date.day.to_s
169
+ when 'WW' then date.day_name
170
+ when 'W' then date.week_day.to_s
171
+ when 'Y' then date.year.to_s
172
+ when 'S' then season_name
173
+ else token
174
+ end
175
+ end
176
+ end
177
+
99
178
  # Convert a Metric Calendar date back to a Gregorian Date.
100
179
  # @param year [Integer] Metric year
101
180
  # @param period_type [String] "turning", "month", or "yule"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: MetricCalendar
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Rabarts
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-16 00:00:00.000000000 Z
11
+ date: 2026-03-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A Ruby library for working with the Metric Calendar — a rational decimal
14
14
  calendar system with 10-day weeks and 12 months of 30 days.