aboutsummaryrefslogtreecommitdiff
path: root/time.odin
diff options
context:
space:
mode:
authorSan Jacobs2026-07-01 16:15:38 +0200
committerSan Jacobs2026-07-01 16:15:38 +0200
commitac28fa0afbc104e6dab14ba25fdcfe961cd706c6 (patch)
treeb6d4a9c5fd74e6e1ef5a6d19a67a9e2b55405b11 /time.odin
parentfa3c3561129ee6e956d31a0cdf8b5b40c1eca43a (diff)
downloadstatics-ac28fa0afbc104e6dab14ba25fdcfe961cd706c6.tar.gz
statics-ac28fa0afbc104e6dab14ba25fdcfe961cd706c6.tar.bz2
statics-ac28fa0afbc104e6dab14ba25fdcfe961cd706c6.zip
New OdinHEADmaster
Diffstat (limited to 'time.odin')
-rw-r--r--time.odin342
1 files changed, 56 insertions, 286 deletions
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..<len(blocks)-count {
- when ODIN_DEBUG do fmt.printf("Putting the contents of %i/%i into %i\n", i+count, len(blocks)-1, i)
- blocks[i] = blocks[i+count]
- }
- for i in len(blocks)-count-1..<len(blocks) {
- blocks[i] = {{0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, 0, "", ""}
- }
- total_timeblocks -= count
-}
-growBlocks :: proc(workday: ^Workday, index: int, count: int = 1) {
- using workday
- fmt.printf("growBlocks() running to make space for %i block(s) at index %i\n", count, index)
- for i: int = len(blocks)-1-count; i>=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..<index+count {
- // blocks[i] = {{0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, 0, ""}
- //}
- total_timeblocks += count
-}
getweekday :: proc(moment: Moment) -> 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 {