package main import math "core:math" import "core:fmt" import "core:strings" import "core:strconv" import "core:slice" import "core:sort" // // --- STRUCTURES --- // Weekday : enum{ Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday } Delta :: struct { minutes : int, hours : int, days : int, } Moment :: struct { minutes : int, hours : int, day : int, month : int, year : int, } Timeblock :: struct { start : Moment, end : Moment, valuefactor : f32, price_reason : string, } Workday :: struct { call : Moment, wrap : Moment, planned_wrap : Moment, blocks : [12]Timeblock, total_timeblocks : int, } // // --- MAJOR PROCEDURES --- // new_workday :: proc(previous_wrap : Moment, calltime : Moment, wraptime : Moment, planned_wraptime : Moment) -> (workday: Workday) { workday.call = calltime workday.wrap = wraptime workday.planned_wrap = planned_wraptime using workday initial_block: Timeblock = {call, // Paragraph 6.7 says that up to 2 hours of unused warned overtime counts as worktime, // though so that at least one hour of the unused overtime is not counted. // (It's unclear if an 8-hour day that ends 3 hours in counts as having 5 hours of unused overtime) max(clamp(sub(planned_wrap, {0, 1, 0}), wrap, add(wrap, {0, 2, 0})), //max(sub(planned_wraptime, {0,1,0}), wrap), add(call, {0, 4, 0})), 0, ""} // ^ Minimum 4 hour day ^ sp_length :: 11 splitpoints:= [sp_length]Moment{ // --$-- Points where the price may change --$-- // // TODO: Replace this terribleness with a system for parsing simple, user-editable rulseset-files add(previous_wrap, {0, 10, 0}), // Sleepbreach, 10 hours after previous wrap, aka. turnover {0, 5, call.day, call.month, call.year}, // 2 hours before 7, aka 5 {0, 6, call.day, call.month, call.year}, // 6 in the morning add(call, {0, 8, 0}), // Normal 8 hours of work add(call, {0, 9, 0}), // 1st hour of overtime is over add(call, {0, 11, 0}), // 3rd hour of overtime is over planned_wraptime, // End of warned overtime add(call, {0, 14, 0}), // The 14-hour mark {0, 22, call.day, call.month, call.year}, // 22:00 in the evening add({0, 23, call.day, call.month, call.year}, {0, 1, 0}), // Midnight add({0, 23, call.day, call.month, call.year}, {0, 7, 0}), // 06:00 the next morning } // Eliminate planned wrap, if it occurs within normal 8-hour period. // This is to make sure the first period of time becomes a pure 8 hours, // which makes detecting the main section of the workday easier. if sortable(splitpoints[6]) < sortable(splitpoints[3]) { splitpoints[6] = splitpoints[3]; } splitpoints_sorted: [sp_length]Moment = splitpoints slice.sort_by(splitpoints_sorted[:], lessMoment) for each_point, i in splitpoints_sorted { fmt.printf("Splitpoint %2i: %s\n", i+1, toString(each_point)) } working_block: Timeblock = initial_block fmt.println("working_block: ", toString(working_block)) j: int = 0 for each_point in splitpoints_sorted { // If each splitpoint moment is within the workday, and is not equal to the start of the current block if sortable(each_point) > sortable(call) && sortable(each_point) < sortable(wrap) && each_point != working_block.start { blocks[j], working_block = timesplit(working_block, each_point) j += 1 //fmt.println("Split and wrote:", j) } } blocks[j] = working_block j += 1 total_timeblocks = j //slice.sort_by(blocks[:], lessTimeblock) for each_block, i in blocks { fmt.printf("Block %2i: %s\n", i+1, toString(each_block)) } // TODO: // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE // BIG BLOCK OF RULES N SHIT GOES HERE return } windIndividual :: proc(input_moment: ^Moment, minutes: int, hours: int, days: int) { // Adding minutes input_moment.minutes += minutes for input_moment.minutes > 59 { input_moment.minutes -= 60 input_moment.hours += 1 } for input_moment.minutes < 0 { input_moment.minutes += 60 input_moment.hours -= 1 } // Adding hours input_moment.hours += hours for input_moment.hours > 23 { input_moment.hours -= 24 input_moment.day += 1 } for input_moment.hours < 0 { input_moment.hours += 24 input_moment.day -= 1 } // Adding days input_moment.day += days current_month_length: int = days_in(input_moment.month, input_moment.year) for input_moment.day > current_month_length { input_moment.day -= current_month_length input_moment.month += 1 if input_moment.month > 12 { input_moment.month -= 12 input_moment.year += 1 } current_month_length = days_in(input_moment.month, input_moment.year) } for input_moment.day < 1 { input_moment.month -= 1 if input_moment.month < 1 { input_moment.month += 12 input_moment.year -= 1 } current_month_length = days_in(input_moment.month, input_moment.year) input_moment.day += current_month_length } return } windDelta :: proc(moment: ^Moment, delta: Delta) { using delta wind(moment, minutes, hours, days) return } wind :: proc{windIndividual, windDelta} timesplit :: proc(block: Timeblock, splitpoint: Moment) -> (first_half: Timeblock, second_half: Timeblock) { // Splits a timeblock at splitpoint. if sortable(splitpoint) < sortable(block.start) || sortable(splitpoint) > sortable(block.end) || splitpoint == block.start || splitpoint == block.end { fmt.println("WHOOPS: Splitpoint is outside timeblock range!") fmt.println("Timeblock:", toString(block)) fmt.println("Splitpoint:", toString(splitpoint)) second_half = block return } first_half = {block.start, splitpoint, block.valuefactor, block.price_reason} second_half = {splitpoint, block.end, block.valuefactor, block.price_reason} return } // // --- BASIC OPERATIONS --- // add :: proc(moment: Moment, delta: Delta) -> (output: Moment) { output = moment wind(&output, delta) return } sub :: proc(moment: Moment, delta: Delta) -> (output: Moment) { output = moment using delta wind(&output, minutes*-1, hours*-1, days*-1) return } maxMoment :: proc(moment_a: Moment, moment_b: Moment) -> Moment { if sortable(moment_a) > sortable(moment_b) do return moment_a return moment_b } maxDelta :: proc(delta_a: Delta, delta_b: Delta) -> Delta { if sortable(delta_a) > sortable(delta_b) do return delta_a return delta_b } max :: proc{maxDelta, maxMoment} minMoment :: proc(moment_a: Moment, moment_b: Moment) -> Moment { if sortable(moment_a) < sortable(moment_b) do return moment_a return moment_b } minDelta :: proc(delta_a: Delta, delta_b: Delta) -> Delta { if sortable(delta_a) < sortable(delta_b) do return delta_a return delta_b } min :: proc{minDelta, minMoment} clampMoment :: proc(moment: Moment, moment_min: Moment, moment_max: Moment) -> Moment { return min(max(moment, moment_min), moment_max) } clampDelta :: proc(delta: Delta, delta_min: Delta, delta_max: Delta) -> Delta { return min(max(delta, delta_min), delta_max) } clamp :: proc{clampMoment, clampDelta} greatMoment :: proc(moment_a: Moment, moment_b: Moment) -> bool { return bool(sortable(moment_a) > sortable(moment_b)) } greatDelta :: proc(delta_a: Delta, delta_b: Delta) -> bool { return bool(sortable(delta_a) > sortable(delta_b)) } great :: proc{greatMoment, greatDelta} lessMoment :: proc(moment_a: Moment, moment_b: Moment) -> bool { return bool(sortable(moment_a) < sortable(moment_b)) } lessDelta :: proc(delta_a: Delta, delta_b: Delta) -> bool { return bool(sortable(delta_a) < sortable(delta_b)) } lessTimeblock :: proc(block_a: Timeblock, block_b: Timeblock) -> bool { if block_b.start == {0, 0, 0, 0, 0} do return true if block_a.start == {0, 0, 0, 0, 0} do return false return bool(sortable(block_a.start) < sortable(block_b.start)) } less :: proc{lessMoment, lessDelta, lessTimeblock} diff :: proc(moment_a: Moment, moment_b: Moment) -> (acc: Delta) { // TODO: Finish writing this // Uses what I call an accumulator-decumulator design // Count how long it takes to approach a benchmark, // and that count is the difference acc = {0, 0, 0} if moment_a == moment_b do return // smallest operand becomes benchmark to approach reverse: bool = sortable(moment_a) < sortable(moment_b) bench : Moment dec : Moment if reverse { bench = moment_a dec = moment_b } else { bench = moment_b dec = moment_a } // It is possible to write something that does this in months at a time, instead of days, // which would be faster, but I am not expecting to have to do this with such // long periods of time, so screw that. for ((dec.year - bench.year) > 1 || (dec.month - bench.month) > 1 || (dec.day - bench.day) > 1) { wind(&dec, 0, 0, -1) acc.days += 1 } for (dec.hours - bench.hours > 1) { wind(&dec, 0, -1, 0) acc.hours += 1 } for acc.hours > 23 { acc.hours -= 24 acc.days += 1 } for dec != bench { wind(&dec, -1, 0, 0) acc.minutes += 1 } for acc.minutes > 59 { acc.minutes -= 60 acc.hours += 1 } // Repeating this is a little bit ugly, but it works for acc.hours > 23 { acc.hours -= 24 acc.days += 1 } return } sortableTimeDelta :: proc(delta: Delta) -> (output: u64) { using delta output, _ = strconv.parse_u64(fmt.tprintf("1%3i%2i%2i", days, hours, minutes)) return } sortableTimeMoment :: proc(moment: Moment) -> (output: u64) { using moment output, _ = strconv.parse_u64(fmt.tprintf("%4i%2i%2i%2i%2i", year, month, day, hours, minutes)) return } sortable :: proc{sortableTimeMoment, sortableTimeDelta} deltaToString :: proc(delta: Delta) -> (output: string) { using delta if hours == 0 && days == 0 && minutes == 0 { return "None" } cat_array : [dynamic]string printed_prev : bool = false if days>0 { buf: [5]byte append(&cat_array, fmt.tprint(days)) if days < 2 { append(&cat_array, " day") } else { append(&cat_array, " days") } printed_prev = true } if hours>0 { if printed_prev do append(&cat_array, ", ") buf: [5]byte append(&cat_array, fmt.tprint(hours)) if hours < 2 { append(&cat_array, " hour") } else { append(&cat_array, " hours") } printed_prev = true } if minutes>0 { if printed_prev do append(&cat_array, ", ") buf: [5]byte append(&cat_array, fmt.tprint(minutes)) if minutes < 2 { append(&cat_array, " minute") } else { append(&cat_array, " minutes") } } output = strings.concatenate(cat_array[:]) return } momentToString :: proc(moment: Moment) -> (output: string) { using moment cat_array: [dynamic]string output = fmt.tprintf("%4i-%2i-%2i %2i:%2i", year, month, day, hours, minutes) return } timeblockToString :: proc(block: Timeblock) -> (output: string) { using block s: [3]string = {toString(start), " -> ", toString(end)} output = strings.concatenate(s[:]) return } toString :: proc{deltaToString, momentToString, timeblockToString} clockprintMoment :: proc(moment: Moment) -> string { using moment return fmt.tprintf("%2i:%2i", hours, minutes) } clockprintTimeblock :: proc(block: Timeblock) -> string { using block return fmt.tprintf("%s -> %s", timeprint(start), timeprint(end)) } timeprint :: proc{clockprintTimeblock, clockprintMoment} days_in :: proc(month: int, year: int) -> int { switch month { case 1: return 31; case 2: if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)){ return 29; } return 28; case 3: return 31; case 4: return 30; case 5: return 31; case 6: return 30; case 7: return 31; case 8: return 31; case 9: return 30; case 10: return 31; case 11: return 30; case 12: return 31; } fmt.printf("You just found month nr: %i. Something is very wrong.\n", month) assert(month < 13 && month > 0) return 30 }