From ac28fa0afbc104e6dab14ba25fdcfe961cd706c6 Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Wed, 1 Jul 2026 16:15:38 +0200 Subject: New Odin --- time.odin | 342 ++++++++++---------------------------------------------------- 1 file changed, 56 insertions(+), 286 deletions(-) (limited to 'time.odin') diff --git a/time.odin b/time.odin index 4c04ad5..31d9d7f 100644 --- a/time.odin +++ b/time.odin @@ -77,215 +77,6 @@ Workday :: struct { // --- 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) - time_max(time_clamp(sub(planned_wrap, {0, 1, 0}), wrap, add(wrap, {0, 2, 0})), - add(call, {0, 4, 0})), 1, "", ""} - // ^ Minimum 4 hour day ^ - - // BUG: I think this is causing a bug where if the day ends at 6 and gets extended, - // it doesn't split the day at 6 and set the work-time before that to be extra expensive - - 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) && // vvvvvvvvvvvvvvvvvvvvvvvv this complication is here to cover - sortable(each_point) < sortable(time_max(wrap, add(call, {0, 4, 0}))) && // for the 4-hour minimum day. - 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 - - // This line is commented out because it shouldn't be needed. - //slice.sort_by(blocks[:], lessTimeblock) - - - fmt.println(total_timeblocks) - - for &block, i in blocks { - if i >= total_timeblocks do break - - //using Weekday - - if lessEq(block.end, splitpoints[0]) do upvalue(&block, 3, "Sleep-breach") // +200% for sleep-breach - if block.start.hours >= 22 do upvalue(&block, 2, "Night") // Work at night, aka. between 22:00 and 06:00 - if (block.end.hours == 6 && block.end.minutes == 0) || block.end.hours <= 5 do upvalue(&block, 2, "Night") // is +100% - if greatEq(block.start, splitpoints[3]) { - upvalue(&block, 1.5, "Overtime") - if getweekday(block.start) == .Saturday do upvalue(&block, 2, "Saturday Overtime") - } - if greatEq(block.start, splitpoints[5]) do upvalue(&block, 2, "Overtime") // End of 3-hour cheap planned overtime - if greatEq(block.start, planned_wrap) && greatEq(block.start, splitpoints[4]) do upvalue(&block, 2, "Overtime") // Unwarned OT - if greatEq(block.start, splitpoints[7]) do upvalue(&block, 3, "Far overtime") // +200% beyond 14th hours is +100% - if getweekday(block.start) == .Saturday do upvalue(&block, 1.5, "Saturday") // Saturdays are +50% - if getweekday(block.start) == .Sunday do upvalue(&block, 2, "Sunday") // Sundays are +100% - - if !(less(call, Moment{0, 7, call.day, call.month, call.year}) && - less(time_min(add(call, Delta{0,8,0}), wrap), Moment{0, 17, call.day, call.month, call.year} )) { - // This was added for rule 6.11c, but in a world without a defined normal workday, - // that rule is already covered already by 6.11g, so this is empty. - } - - - // Holidays! - if (block.start.day==1) && (block.start.month==1) { upvalue(&block, 2, "New year"); continue} - if (block.start.day==1) && (block.start.month==5) { upvalue(&block, 2, "1st of May"); continue} - if (block.start.day==17) && (block.start.month==5) { upvalue(&block, 2, "17th of May"); continue} - if (block.start.day==25 || block.start.day==26) && block.start.month==12 { upvalue(&block, 2, "Christmas"); continue} - easter: Moment = gaussEaster(block.start.year) - if (block.start.day == sub(easter, {0,0,3}).day) && block.start.month == sub(easter, {0,0,3}).month { upvalue(&block, 2, "Maundy Thursday"); continue} - if (block.start.day == sub(easter, {0,0,2}).day) && block.start.month == sub(easter, {0,0,2}).month { upvalue(&block, 2, "Good Friday"); continue} - if (block.start.day == easter.day) && (block.start.month == easter.month) { upvalue(&block, 2, "Easter"); continue} - if (block.start.day == add(easter, {0,0,1}).day) && (block.start.month == add(easter, {0,0,1}).month) { upvalue(&block, 2, "Easter"); continue} - if (block.start.day == add(easter, {0,0,39}).day) && (block.start.month == add(easter, {0,0,39}).month) { upvalue(&block, 2, "Feast of the Ascension"); continue} - if (block.start.day == add(easter, {0,0,49}).day) && (block.start.month == add(easter, {0,0,49}).month) { upvalue(&block, 2, "Pentecost"); continue} - if (block.start.day == add(easter, {0,0,50}).day) && (block.start.month == add(easter, {0,0,50}).month) { upvalue(&block, 2, "Pentecost Monday"); continue} - } - - for each_block, i in blocks { - fmt.printf("Block %2i: %s $f: %i%% %s\n", i+1, toString(each_block), int((each_block.value-1)*100), each_block.reason) - price += f32(f64(dayrate/7.5) * f64(hourcount(each_block)) * f64(each_block.value)) - } - - return -} - -lunch :: proc(workday: ^Workday, lunch_start: Moment, lunch_end: Moment) { - - // - // This basically cuts out part of the workday - // - // |-------|---|-------|----|---------|--------------| - // |--lunch--| - // |-------|---|----| |-------|--------------| - // This ^ works now! - - if lunch_start == lunch_end do return - assert(less(lunch_start, lunch_end), "ERROR: Bad Lunch! Lunch ends before it starts") - - start_index: int - end_index: int - for block, i in workday.blocks { - if (great(lunch_start, block.start) && less(lunch_start, block.end)) || (block.start == lunch_start) { - start_index = i - } - if (great(lunch_end, block.start) && less(lunch_end, block.end)) || (block.end == lunch_end) { - end_index = i - } - } - - assert(start_index <= end_index, "ERROR: Bad Lunch! start_index greater than end_index") - - span: int = end_index - start_index - - // TODO: This is bad and can definitely be simplified and done in a more principled way - // But right now it works perfectly, and is much better than it used to be in the C++ version - switch span { - case 0: - fmt.println("Start and end are in the same block") - switch { - case (lunch_start == workday.blocks[start_index].start) && (lunch_end == workday.blocks[end_index].end): - popBlock(workday, start_index) - case lunch_start == workday.blocks[start_index].start: - workday.blocks[start_index].start = lunch_end - case lunch_end == workday.blocks[end_index].end: - workday.blocks[end_index].end = lunch_start - case: - growBlocks(workday, start_index) - end_index += 1 - workday.blocks[start_index].end = lunch_start - workday.blocks[end_index].start = lunch_end - } - - case 1: - fmt.println("Start and end span one gap") - switch { - case (lunch_start == workday.blocks[start_index].start) && (lunch_end == workday.blocks[end_index].end): - popBlock(workday, start_index, 2) - case lunch_start == workday.blocks[start_index].start: - workday.blocks[end_index].start = lunch_end - popBlock(workday, start_index) - case lunch_end == workday.blocks[end_index].end: - workday.blocks[start_index].end = lunch_start - popBlock(workday, end_index) - case: - workday.blocks[end_index].start = lunch_end - workday.blocks[start_index].end = lunch_start - } - - case 2..=len(workday.blocks): - fmt.println("Start and end span more than one gap") - switch { - case (lunch_start == workday.blocks[start_index].start) && (lunch_end == workday.blocks[end_index].end): - popBlock(workday, start_index, span+1) - case lunch_start == workday.blocks[start_index].start: - workday.blocks[end_index].start = lunch_end - popBlock(workday, start_index, span) - case lunch_end == workday.blocks[end_index].end: - workday.blocks[start_index].end = lunch_start - popBlock(workday, start_index+1, span) - case: - workday.blocks[end_index].start = lunch_end - workday.blocks[start_index].end = lunch_start - popBlock(workday, start_index+1, span-1) - } - } -} - windIndividual :: proc(input_moment: ^Moment, minutes: int, hours: int, @@ -340,8 +131,10 @@ windIndividual :: proc(input_moment: ^Moment, return } windByDelta :: proc(moment: ^Moment, delta: Delta) { - using delta - wind(moment, minutes, hours, days) + wind(moment, + delta.minutes, + delta.hours, + delta.days) return } wind :: proc{windIndividual, windByDelta} @@ -378,13 +171,13 @@ importICS :: proc(path: string, verbose: bool = false) -> ([dynamic]Timeblock, b c : Timeblock - raw, ok := os.read_entire_file_from_filename(path) + raw, err := os.read_entire_file_from_path(path, allocator=context.temp_allocator) content := string(raw) i := 1 line_nr := 1 - if !ok { + if err != nil { // TODO: Actually check the content to see if it is an ICS file. fmt.eprintf("ERROR: No file found at: \"%v\"", path) return output, false @@ -496,8 +289,10 @@ add :: proc(moment: Moment, delta: Delta) -> (output: Moment) { } sub :: proc(moment: Moment, delta: Delta) -> (output: Moment) { output = moment - using delta - wind(&output, minutes*-1, hours*-1, days*-1) + wind(&output, + delta.minutes*-1, + delta.hours*-1, + delta.days*-1) return } maxMoment :: proc(moment_a: Moment, moment_b: Moment) -> Moment { @@ -626,33 +421,38 @@ diff :: proc(moment_a: Moment, moment_b: Moment) -> (acc: Delta) { } sortableTimeDelta :: proc(delta: Delta) -> (output: u64) { - using delta - output, _ = strconv.parse_u64(fmt.tprintf("1%3i%2i%2i", days, hours, minutes)) + output, _ = strconv.parse_u64(fmt.tprintf("1%3i%2i%2i", + delta.days, + delta.hours, + delta.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)) + output, _ = strconv.parse_u64(fmt.tprintf("%4i%2i%2i%2i%2i", + moment.year, + moment.month, + moment.day, + moment.hours, + moment.minutes)) return } sortable :: proc{sortableTimeMoment, sortableTimeDelta} deltaToString :: proc(delta: Delta) -> (output: string) { - using delta - if hours == 0 && - days == 0 && - minutes == 0 { + if delta.hours == 0 && + delta.days == 0 && + delta.minutes == 0 { return "None" - } + } cat_array : [dynamic]string printed_prev : bool = false - if days>0 { + if delta.days>0 { buf: [5]byte - append(&cat_array, fmt.tprint(days)) - if days < 2 { + append(&cat_array, fmt.tprint(delta.days)) + if delta.days < 2 { append(&cat_array, " day") } else { append(&cat_array, " days") @@ -660,13 +460,13 @@ deltaToString :: proc(delta: Delta) -> (output: string) { printed_prev = true } - if hours>0 { + if delta.hours>0 { if printed_prev do append(&cat_array, ", ") buf: [5]byte - append(&cat_array, fmt.tprint(hours)) - if hours < 2 { + append(&cat_array, fmt.tprint(delta.hours)) + if delta.hours < 2 { append(&cat_array, " hour") } else { append(&cat_array, " hours") @@ -674,13 +474,13 @@ deltaToString :: proc(delta: Delta) -> (output: string) { printed_prev = true } - if minutes>0 { + if delta.minutes>0 { if printed_prev do append(&cat_array, ", ") buf: [5]byte - append(&cat_array, fmt.tprint(minutes)) - if minutes < 2 { + append(&cat_array, fmt.tprint(delta.minutes)) + if delta.minutes < 2 { append(&cat_array, " minute") } else { append(&cat_array, " minutes") @@ -693,18 +493,21 @@ deltaToString :: proc(delta: Delta) -> (output: string) { 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) + output = fmt.tprintf("%4i-%2i-%2i %2i:%2i", + moment.year, + moment.month, + moment.day, + moment.hours, + moment.minutes) return } timeblockToString :: proc(block: Timeblock) -> (output: string) { - using block s: [5]string = {} - output = fmt.aprintf("%s -> %02d:%02d | %0.3f hrs | %s", toString(start), end.hours, end.minutes, hourcount(block), title) + output = fmt.aprintf("%s -> %02d:%02d | %0.3f hrs | %s", + toString(block.start), block.end.hours, block.end.minutes, hourcount(block), block.title) return } toString :: proc{deltaToString, momentToString, timeblockToString} @@ -712,50 +515,22 @@ toString :: proc{deltaToString, momentToString, timeblockToString} clockprintMoment :: proc(moment: Moment) -> string { - using moment - return fmt.tprintf("%2i:%2i", hours, minutes) + return fmt.tprintf("%2i:%2i", moment.hours, moment.minutes) } clockprintTimeblock :: proc(block: Timeblock) -> string { - using block - return fmt.tprintf("%s -> %s", clockprint(start), clockprint(end)) + return fmt.tprintf("%s -> %s", clockprint(block.start), clockprint(block.end)) } clockprint :: proc{clockprintTimeblock, clockprintMoment} dayprintMoment :: proc(moment: Moment) -> string { - using moment - return fmt.tprintf("%4i-%2i-%2i", year, month, day) + return fmt.tprintf("%4i-%2i-%2i", moment.year, moment.month, moment.day) } dayprintTimeblock :: proc(block: Timeblock) -> string { - using block - return fmt.tprintf("%s -> %s", dayprint(start), dayprint(end)) + return fmt.tprintf("%s -> %s", dayprint(block.start), dayprint(block.end)) } dayprint :: proc{dayprintTimeblock, dayprintMoment} -popBlock :: proc(workday: ^Workday, index: int, count: int = 1) { - using workday - when ODIN_DEBUG do fmt.printf("popBlock() running to remove %i block(s) from index %i\n", count, index) - for i in index..=index; i-=1 { - fmt.printf("Putting the contents of %i/%i into %i\n", i+count, len(blocks)-1, i) - blocks[i+count] = blocks[i] - } - //for i in index.. Weekday { y: int = moment.year @@ -765,28 +540,23 @@ getweekday :: proc(moment: Moment) -> Weekday { } hourcount :: proc(block: Timeblock) -> f32 { - using block - delta: Delta = diff(end, start) - using delta - return f32(f32(minutes)/60 + - f32(hours) + - f32(days) * 24) + delta: Delta = diff(block.end, block.start) + return f32(f32(delta.minutes)/60 + + f32(delta.hours) + + f32(delta.days) * 24) } minutecount :: proc(block: Timeblock) -> int { - using block - delta: Delta = diff(end, start) - using delta - return (minutes) + - (hours*60) + - (days*24*60) + delta: Delta = diff(block.end, block.start) + return (delta.minutes) + + (delta.hours*60) + + (delta.days*24*60) } daycount :: proc(delta: Delta) -> f32 { - using delta assert(delta != {0,0,0}) - return f32(f32(minutes)/60/24 + - f32(hours)/24 + - f32(days) ) + return f32(f32(delta.minutes)/60/24 + + f32(delta.hours)/24 + + f32(delta.days) ) } days_in :: proc(month: int, year: int) -> int { -- cgit v1.2.3