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 +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +22 -0
- data/README.md +74 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/salario/easter.rb +20 -0
- data/lib/salario/holidays.rb +66 -0
- data/lib/salario/version.rb +3 -0
- data/lib/salario/working_hours.rb +51 -0
- data/lib/salario.rb +20 -0
- data/salario.gemspec +29 -0
- metadata +100 -0
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
data/.travis.yml
ADDED
data/Gemfile
ADDED
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
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,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,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: []
|