Module: MppReader::Decode
- Defined in:
- lib/mpp_reader/decode.rb
Overview
Decoders for MS Project’s binary value encodings, ported from MPXJ MPPUtility. The epoch is 1983-12-31; dates are u16 days since the epoch; times of day and durations are stored in tenths of a minute; timestamps combine a u16 time (6-second units) with a u16 day count.
Constant Summary collapse
- EPOCH_DATE =
Date.new(1983, 12, 31)
- DURATION_UNITS_MASK =
0x1F- DURATION_UNITS =
{ 3 => :minutes, 4 => :elapsed_minutes, 5 => :hours, 6 => :elapsed_hours, 7 => :days, 8 => :elapsed_days, 9 => :weeks, 10 => :elapsed_weeks, 11 => :months, 12 => :elapsed_months, 19 => :percent, 20 => :elapsed_percent }.freeze
- TENTHS_PER_UNIT =
Divisors converting tenths-of-minutes to each unit, assuming MS Project’s defaults (8h days, 40h weeks, 20-day months).
{ minutes: 10, elapsed_minutes: 10, hours: 600, elapsed_hours: 600, days: 4800, elapsed_days: 14_400, weeks: 24_000, elapsed_weeks: 100_800, months: 96_000, elapsed_months: 432_000 }.freeze
Class Method Summary collapse
-
.adjusted_duration(value, units, minutes_per_day:, minutes_per_week:, days_per_month:) ⇒ Object
Like duration, but day/week/month conversions honour the project’s configured working time instead of the defaults.
- .date(data, offset) ⇒ Object
-
.duration(value, units) ⇒ Object
value is in tenths of a minute; converts using MS Project’s default hours-per-day assumptions.
- .duration_units(type, default: :days) ⇒ Object
- .timestamp(data, offset) ⇒ Object
- .timestamp_from_tenths(data, offset) ⇒ Object
- .to_time(date, seconds_of_day) ⇒ Object
Class Method Details
.adjusted_duration(value, units, minutes_per_day:, minutes_per_week:, days_per_month:) ⇒ Object
Like duration, but day/week/month conversions honour the project’s configured working time instead of the defaults.
87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/mpp_reader/decode.rb', line 87 def adjusted_duration(value, units, minutes_per_day:, minutes_per_week:, days_per_month:) tenths_per_unit = case units when :days then minutes_per_day * 10 when :weeks then minutes_per_week * 10 when :months then minutes_per_day * days_per_month * 10 end if tenths_per_unit.nil? || tenths_per_unit.zero? duration(value, units) else Duration.new(value.to_f / tenths_per_unit, units) end end |
.date(data, offset) ⇒ Object
43 44 45 46 47 48 |
# File 'lib/mpp_reader/decode.rb', line 43 def date(data, offset) days = data.byteslice(offset, 2).to_s.unpack1("v") return nil if days.nil? || days == 0xFFFF EPOCH_DATE + days end |
.duration(value, units) ⇒ Object
value is in tenths of a minute; converts using MS Project’s default hours-per-day assumptions.
81 82 83 |
# File 'lib/mpp_reader/decode.rb', line 81 def duration(value, units) Duration.new(value.to_f / TENTHS_PER_UNIT.fetch(units, 1), units) end |
.duration_units(type, default: :days) ⇒ Object
72 73 74 75 76 77 |
# File 'lib/mpp_reader/decode.rb', line 72 def duration_units(type, default: :days) code = type & DURATION_UNITS_MASK return default if code == 21 DURATION_UNITS.fetch(code, :days) end |
.timestamp(data, offset) ⇒ Object
50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/mpp_reader/decode.rb', line 50 def (data, offset) days = data.byteslice(offset + 2, 2).to_s.unpack1("v") return nil if days.nil? || days <= 1 || days == 0xFFFF time = data.byteslice(offset, 2).unpack1("v") time = 0 if time == 0xFFFF seconds = time * 6 # Very small day counts show as NA in MS Project; a non-zero seconds # component distinguishes NA from real values (MPXJ heuristic). return nil if days < 100 && (seconds % 60) != 0 to_time(EPOCH_DATE + days, seconds) end |
.timestamp_from_tenths(data, offset) ⇒ Object
64 65 66 67 68 69 70 |
# File 'lib/mpp_reader/decode.rb', line 64 def (data, offset) tenths = data.byteslice(offset, 4).to_s.unpack1("V") return nil if tenths.nil? seconds = tenths * 6 to_time(EPOCH_DATE + (seconds / 86_400), seconds % 86_400) end |
.to_time(date, seconds_of_day) ⇒ Object
101 102 103 |
# File 'lib/mpp_reader/decode.rb', line 101 def to_time(date, seconds_of_day) Time.new(date.year, date.month, date.day) + seconds_of_day end |