salario 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 476aa3eb3b59f658646131844695c7fec61e027baafe24a8ce71b79cc18ecab0
4
+ data.tar.gz: 93dec5df3a8c34f9b0921f3d5e43e6ca8d0a1bf130ae7f1e4324dfbc189f2a39
5
+ SHA512:
6
+ metadata.gz: 91f34ffe433b25d0ff21c22d7f5150ebc240d3d23526221dddefdf4840bb90ae9533d9822aeb83e254f6bd4821bdbfd6d65493681866f4037b0960334b34bc12
7
+ data.tar.gz: 315fa8499b33aaa30abb8121fbf4674123ae5368091d81c2c3c22bb54539142653f50121f681692f3edad71887caf527c99efde1ff49e9fc85d324917fbc8e85
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.10
7
+ before_install: gem install bundler -v 1.17.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in salario.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,22 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ salario (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ minitest (5.11.3)
10
+ rake (12.3.3)
11
+
12
+ PLATFORMS
13
+ ruby
14
+
15
+ DEPENDENCIES
16
+ bundler (>= 1.17)
17
+ minitest (~> 5.0)
18
+ rake (>= 10.0)
19
+ salario!
20
+
21
+ BUNDLED WITH
22
+ 1.17.2
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # Salario
2
+
3
+ A Ruby gem for Swedish salary system calculations.
4
+ Provides workable hours per month and public holidays (*röda dagar*) according to Swedish law (SFS 1989:253).
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'salario'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install salario
21
+
22
+ ## Usage
23
+
24
+ ### Public holidays
25
+
26
+ Returns all Swedish public holidays (*allmänna helgdagar*) for a given year, including fixed dates, Easter-dependent dates, and movable Saturdays.
27
+
28
+ ```ruby
29
+ Salario.public_holidays(2026)
30
+ # => [#<Holiday date=2026-01-01 name="New Year's Day" name_sv="Nyårsdagen">,
31
+ # #<Holiday date=2026-01-06 name="Epiphany" name_sv="Trettondedag jul">,
32
+ # ...]
33
+ ```
34
+
35
+ Each holiday has `date`, `name` (English), and `name_sv` (Swedish) attributes.
36
+
37
+ Historical accuracy: National Day (June 6) is included from 2005 onward; Whit Monday (*Annandag pingst*) is included before 2005.
38
+
39
+ ### Working hours per month
40
+
41
+ Calculates workable hours for a specific month, accounting for weekends and public holidays.
42
+
43
+ ```ruby
44
+ result = Salario.working_hours(2026, 4)
45
+
46
+ result.weekdays # => 22
47
+ result.holidays_on_weekdays # => 2 (Good Friday, Easter Monday)
48
+ result.workable_days # => 20
49
+ result.workable_hours # => 160
50
+ result.holiday_list # => detailed holiday structs for the month
51
+ ```
52
+
53
+ #### Options
54
+
55
+ **Custom hours per day** — for collective agreements with shorter weeks (e.g. 37.5h):
56
+
57
+ ```ruby
58
+ Salario::WorkingHours.for_month(2026, 3, hours_per_day: 7.5)
59
+ ```
60
+
61
+ ## What's included
62
+
63
+ | Category | Details |
64
+ |---|---|
65
+ | **Public holidays** | 14 *röda dagar* per SFS 1989:253 — fixed dates, Easter-dependent, and movable Saturdays (Midsummer, All Saints) |
66
+ | **Easter** | Computed via the Anonymous Gregorian algorithm, verified against known dates 2020–2030 |
67
+
68
+ ## Development
69
+
70
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
71
+
72
+ ## Contributing
73
+
74
+ Bug reports and pull requests are welcome on GitHub at https://github.com/antonysastre/salario.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "salario"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,20 @@
1
+ require "date"
2
+
3
+ module Salario
4
+ # Computes Easter Sunday for a given year using the Anonymous Gregorian algorithm.
5
+ module Easter
6
+ def self.sunday(year)
7
+ metonic_cycle = year % 19
8
+ century, year_in_century = year.divmod(100)
9
+ leap_correction, leap_remainder = century.divmod(4)
10
+ sync_correction = (century + 8) / 25
11
+ lunar_correction = (century - sync_correction + 1) / 3
12
+ paschal_moon = (19 * metonic_cycle + century - leap_correction - lunar_correction + 15) % 30
13
+ day_correction, day_remainder = year_in_century.divmod(4)
14
+ weekday_offset = (32 + 2 * leap_remainder + 2 * day_correction - paschal_moon - day_remainder) % 7
15
+ leap_adjustment = (metonic_cycle + 11 * paschal_moon + 22 * weekday_offset) / 451
16
+ month, day = (paschal_moon + weekday_offset - 7 * leap_adjustment + 114).divmod(31)
17
+ Date.new(year, month, day + 1)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,66 @@
1
+ require "date"
2
+
3
+ module Salario
4
+ # Swedish public holidays (allmänna helgdagar) per SFS 1989:253.
5
+ #
6
+ # Returns an array of hashes: { date:, name:, name_sv: }
7
+ module Holidays
8
+ def self.for_year(year)
9
+ easter = Easter.sunday(year)
10
+
11
+ holidays = [
12
+ # Fixed holidays
13
+ fixed(year, 1, 1, "New Year's Day", "Nyårsdagen"),
14
+ fixed(year, 1, 6, "Epiphany", "Trettondedag jul"),
15
+ fixed(year, 5, 1, "May Day", "Första maj"),
16
+ fixed(year, 12, 25, "Christmas Day", "Juldagen"),
17
+ fixed(year, 12, 26, "Second Day of Christmas", "Annandag jul"),
18
+
19
+ # National Day (public holiday since 2005)
20
+ (fixed(year, 6, 6, "National Day of Sweden", "Sveriges nationaldag") if year >= 2005),
21
+
22
+ # Easter-dependent holidays
23
+ movable(easter - 2, "Good Friday", "Långfredagen"),
24
+ movable(easter - 1, "Easter Saturday", "Påskafton"),
25
+ movable(easter, "Easter Sunday", "Påskdagen"),
26
+ movable(easter + 1, "Easter Monday", "Annandag påsk"),
27
+ movable(easter + 39, "Ascension Day", "Kristi himmelsfärdsdag"),
28
+ movable(easter + 49, "Whit Sunday", "Pingstdagen"),
29
+
30
+ # Whit Monday was a public holiday until 2004
31
+ (movable(easter + 50, "Whit Monday", "Annandag pingst") if year < 2005),
32
+
33
+ # Midsummer's Day: Saturday between June 20-26
34
+ saturday_between(year, 6, 20, 6, 26, "Midsummer's Day", "Midsommardagen"),
35
+
36
+ # All Saints' Day: Saturday between Oct 31 - Nov 6
37
+ saturday_between(year, 10, 31, 11, 6, "All Saints' Day", "Alla helgons dag"),
38
+ ].compact
39
+
40
+ holidays.sort_by(&:date)
41
+ end
42
+
43
+ Holiday = Struct.new(:date, :name, :name_sv, keyword_init: true)
44
+
45
+ class << self
46
+ private
47
+
48
+ def fixed(year, month, day, name, name_sv)
49
+ Holiday.new(date: Date.new(year, month, day), name: name, name_sv: name_sv)
50
+ end
51
+
52
+ def movable(date, name, name_sv)
53
+ Holiday.new(date: date, name: name, name_sv: name_sv)
54
+ end
55
+
56
+ def saturday_between(year, start_month, start_day, end_month, end_day, name, name_sv)
57
+ start_date = Date.new(year, start_month, start_day)
58
+ end_date = Date.new(year, end_month, end_day)
59
+ date = start_date
60
+ date += 1 until date.saturday?
61
+ raise Error, "No Saturday found in range #{start_date}..#{end_date}" if date > end_date
62
+ Holiday.new(date: date, name: name, name_sv: name_sv)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,3 @@
1
+ module Salario
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,51 @@
1
+ require "date"
2
+
3
+ module Salario
4
+ # Calculates workable hours for a given month, accounting for public holidays.
5
+ module WorkingHours
6
+ Result = Struct.new(
7
+ :year, :month,
8
+ :total_days, :weekdays, :holidays_on_weekdays,
9
+ :workable_days, :workable_hours,
10
+ :holiday_list,
11
+ keyword_init: true
12
+ )
13
+
14
+ # Returns a Result for the given year/month.
15
+ #
16
+ # Options:
17
+ # hours_per_day: standard hours per full working day (default: 8)
18
+ def self.for_month(year, month, hours_per_day: STANDARD_HOURS_PER_DAY)
19
+ first = Date.new(year, month, 1)
20
+ last = Date.new(year, month, -1)
21
+ range = first..last
22
+
23
+ all_holidays = Holidays.for_year(year)
24
+
25
+ # Holidays that fall on a weekday (Mon-Fri) in this month
26
+ holidays_in_month = all_holidays.select { |h| range.cover?(h.date) && weekday?(h.date) }
27
+
28
+ weekdays = range.count { |d| weekday?(d) }
29
+ holiday_weekday_count = holidays_in_month.size
30
+
31
+ workable_days = weekdays - holiday_weekday_count
32
+ workable_hours = workable_days * hours_per_day
33
+
34
+ Result.new(
35
+ year: year,
36
+ month: month,
37
+ total_days: range.count,
38
+ weekdays: weekdays,
39
+ holidays_on_weekdays: holiday_weekday_count,
40
+ workable_days: workable_days,
41
+ workable_hours: workable_hours,
42
+ holiday_list: holidays_in_month,
43
+ )
44
+ end
45
+
46
+ def self.weekday?(date)
47
+ !date.saturday? && !date.sunday?
48
+ end
49
+ private_class_method :weekday?
50
+ end
51
+ end
data/lib/salario.rb ADDED
@@ -0,0 +1,20 @@
1
+ require "salario/version"
2
+ require "salario/easter"
3
+ require "salario/holidays"
4
+ require "salario/working_hours"
5
+
6
+ module Salario
7
+ class Error < StandardError; end
8
+
9
+ STANDARD_HOURS_PER_DAY = 8
10
+
11
+ # Returns all public holidays (red days) for a given year.
12
+ def self.public_holidays(year)
13
+ Holidays.for_year(year)
14
+ end
15
+
16
+ # Returns a WorkingHours summary for a given year and month.
17
+ def self.working_hours(year, month)
18
+ WorkingHours.for_month(year, month)
19
+ end
20
+ end
data/salario.gemspec ADDED
@@ -0,0 +1,29 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "salario/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "salario"
8
+ spec.version = Salario::VERSION
9
+ spec.authors = ["Antony Sastre"]
10
+ spec.email = ["antony@northcollective.se"]
11
+
12
+ spec.summary = "Swedish salary system calculations"
13
+ spec.description = "Calculate workable hours and statutory public holidays according to Swedish labour law."
14
+ spec.homepage = "https://github.com/antonysastre/salario"
15
+ spec.license = "MIT"
16
+
17
+ spec.required_ruby_version = ">= 2.5.0"
18
+
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_development_dependency "bundler", ">= 1.17"
27
+ spec.add_development_dependency "rake", ">= 10.0"
28
+ spec.add_development_dependency "minitest", "~> 5.0"
29
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: salario
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Antony Sastre
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-03-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ description: Calculate workable hours and statutory public holidays according to Swedish
56
+ labour law.
57
+ email:
58
+ - antony@northcollective.se
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - Gemfile.lock
67
+ - README.md
68
+ - Rakefile
69
+ - bin/console
70
+ - bin/setup
71
+ - lib/salario.rb
72
+ - lib/salario/easter.rb
73
+ - lib/salario/holidays.rb
74
+ - lib/salario/version.rb
75
+ - lib/salario/working_hours.rb
76
+ - salario.gemspec
77
+ homepage: https://github.com/antonysastre/salario
78
+ licenses:
79
+ - MIT
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 2.5.0
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubygems_version: 3.0.3.1
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Swedish salary system calculations
100
+ test_files: []