aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.odin69
-rw-r--r--src/time.odin490
-rw-r--r--src/utils.odin10
3 files changed, 569 insertions, 0 deletions
diff --git a/src/main.odin b/src/main.odin
new file mode 100644
index 0000000..2030c2f
--- /dev/null
+++ b/src/main.odin
@@ -0,0 +1,69 @@
+package main
+
+import "core:fmt"
+
+main :: proc() {
+ test()
+ new_workday({0, 0, 1, 1, 1},
+ {00, 08, 5, 5, 2023},
+ {00, 23, 5, 5, 2023},
+ {30, 21, 5, 5, 2023})
+}
+
+test :: proc() {
+
+ fmt.println("\n--- TESTING PRINT STUFFS ---")
+
+ test_delta: Delta = {1, 1, 1}
+ fmt.println(toString(test_delta))
+ test_delta = {0, 0, 0}
+ fmt.println(toString(test_delta))
+ test_delta = {0, 12, 2}
+ fmt.printf("I've been waiting for %s! That's a long time!\n", toString(test_delta))
+
+ start: Moment = {45, 8, 20, 4, 2023}
+ end: Moment = add(start, test_delta)
+ block: Timeblock = {start, end, 0, ""}
+ fmt.println(toString(block))
+
+ fmt.println(timeprint(block))
+
+
+
+ fmt.println("\n--- TESTING WINDING ---")
+
+ test_moment: Moment = {45, 8, 30, 12, 2023}
+ fmt.println(toString(test_moment))
+ test_delta = {30, 1, 2}
+ fmt.printf("Winding forward by %s\n", toString(test_delta))
+ wind(&test_moment, 30, 1, 2)
+ fmt.println(toString(test_moment))
+
+
+
+ fmt.println("\n--- TESTING OPERATIONS ---")
+
+ fmt.printf("So far test_moment, holds: %s.\n", toString(test_moment))
+ fmt.printf("Using add(), that is: %s ephemerally,\n", toString(add(test_moment, test_delta)))
+ fmt.printf("but test_moment, is still: %s.\n", toString(test_moment))
+ fmt.printf("Using sub(), that is: %s ephemerally,\n", toString(sub(test_moment, test_delta)))
+ fmt.printf("but test_moment, is still: %s.\n", toString(test_moment))
+
+
+
+ fmt.println("\n--- TESTING DELTA & SORTABLE ---")
+
+ fmt.printf("The sortable() version of %s is %i.\n", toString(test_moment), sortable(test_moment))
+ fmt.printf("The sortable() version of %s is %i.\n", toString(test_delta), sortable(test_delta))
+
+ fmt.printf("The diff() between %s and %s is %s.\n",
+ toString(test_moment),
+ toString(add(test_moment, test_delta)),
+ toString(diff(test_moment, add(test_moment, test_delta))))
+
+
+ //for i: int=1; i < 14; i += 1 {
+ // fmt.println(days_in(i, 2023))
+ //}
+ fmt.println("")
+} \ No newline at end of file
diff --git a/src/time.odin b/src/time.odin
new file mode 100644
index 0000000..6acfc20
--- /dev/null
+++ b/src/time.odin
@@ -0,0 +1,490 @@
+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 : [15]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(planned_wrap, {0, 2, 0})),
+ 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, 22, 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)
+
+ working_block: Timeblock = initial_block
+
+ j: int = 0
+ for each_moment 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_moment) > sortable(call) && sortable(each_moment) < sortable(wrap) && each_moment != initial_block.start {
+ blocks[j], working_block = timesplit(working_block, each_moment)
+ 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
+}
diff --git a/src/utils.odin b/src/utils.odin
new file mode 100644
index 0000000..1ef26eb
--- /dev/null
+++ b/src/utils.odin
@@ -0,0 +1,10 @@
+package main
+
+import "core:slice"
+import "core:strings"
+
+concatenate :: proc(old_array: [dynamic]string) -> string {
+ new_array := slice.clone(old_array[:])
+ defer delete(new_array)
+ return strings.concatenate(new_array)
+} \ No newline at end of file