Programmera med veckor

Marcus Olsson,

Jag har de senaste månaderna jobbat på ett projekt som kretsar mycket kring veckor och veckonummer, där en vecka är "basen" och som hanterar och verifierar tillhörande objekt.

Men – ju mer jag har jobbat med detta desto mer så har jag insett att det är både svårt och kanske en ganska dålig idé. Datum och kalendrar är märkliga ting.

Först och främst så har vi ju ett problem som många känner till, ett år har inte alltid 52 veckor. Inte allt för sällan så innehåller året 53 veckor; år 2020 och 2026 är exempel på detta. Inte allt för stora problem egentligen, ganska enkelt att anpassa sig efter och bygga runt.

Desto större problem däremot blir det om du börjar bygga funktioner som t.ex. ska hämta alla dagarna för ett visst veckonummer och år. Eller om du kanske vill se efter vilken den första och sista dagen en viss vecka är.

Ser man efter på 2016 som exempel ser vi ett problem för vecka 52. Veckan börjar på måndagen den 26:e december och slutar på söndagen den 1:a januari. Vecka 52 har då alltså ett spann över två år; 2016 och 2017.

Ett ännu större problem är 2019. Vecka 1 2019 innehåller nämligen den sista dagen för 2018. För 2018 upprepar sig alltså vecka 1. I ett scenario där du har en funktion där du vill hämta alla dagar för vecka 1 2018 – då blir frågan helt enkelt "vilken vecka 1"? Den finns ju två gånger; i januari och december.

Veckokalender 2018

1Vecka 1
2Vecka 2
3Vecka 3
4...
5Vecka 51
6Vecka 52
7Vecka 1
1Vecka 1
2Vecka 2
3Vecka 3
4...
5Vecka 51
6Vecka 52
7Vecka 1

Ett annat praktiskt exempel; i PHP för att parse:a ut datum för en vecka använder man t.ex. "2018W42". Är veckonumret under 10 så måste det föregås med en nolla. Alltså för vecka 1 2019:

1$date = date('Y', strtotime('2019W01'));
2// $date = 2018
1$date = date('Y', strtotime('2019W01'));
2// $date = 2018

2018?! Här har vi genast stora problem, man förväntar sig inte (eller inte jag i alla fall) att få ut 2018 när man försöker parse:a vecka 1 för år 2019.

Föreställ dig att användaren matar in ett veckonummer och år där man applicerar uträkningar i flera steg – kan ganska snabbt bli extremt fel. Ett exempel med lite "hitte-på-kod":

1/**
2 * Fetch all weeks in the
3 * same month as 'year_week'
4 */
5$input = $_GET['year_week']; // input = 2019W01
6$start = date('Y-m-t', strtotime($input)); // 20181231!
7$end = date('t', strtotime($start)); // t = number of days in month
8 
9$weeks = [];
10while ($end >= 0) {
11 
12 // $start - $end days
13 $week = date('W', strtotime($start . ' - ' . $end . ' days'));
14 
15 if (!in_array($week, $weeks)) {
16 $weeks[] = $week;
17 }
18 $end--;
19}
20 
21var_dump($weeks);
22 
23//array (size=6)
24// 0 => string '48' (length=2)
25// 1 => string '49' (length=2)
26// 2 => string '50' (length=2)
27// 3 => string '51' (length=2)
28// 4 => string '52' (length=2)
29// 5 => string '01' (length=2)
1/**
2 * Fetch all weeks in the
3 * same month as 'year_week'
4 */
5$input = $_GET['year_week']; // input = 2019W01
6$start = date('Y-m-t', strtotime($input)); // 20181231!
7$end = date('t', strtotime($start)); // t = number of days in month
8 
9$weeks = [];
10while ($end >= 0) {
11 
12 // $start - $end days
13 $week = date('W', strtotime($start . ' - ' . $end . ' days'));
14 
15 if (!in_array($week, $weeks)) {
16 $weeks[] = $week;
17 }
18 $end--;
19}
20 
21var_dump($weeks);
22 
23//array (size=6)
24// 0 => string '48' (length=2)
25// 1 => string '49' (length=2)
26// 2 => string '50' (length=2)
27// 3 => string '51' (length=2)
28// 4 => string '52' (length=2)
29// 5 => string '01' (length=2)

Man skulle kunna tro att man får ut alla veckor för januari 2019, man hade t.o.m. året som en parameter – men icke.

Efter lite letande så förklaras även veckosystemet i ISO 8601 (Wikipedia-länk, eller om du vill köpa den för dryga 1000kr från SIS) – standarden som styr de olika datumformaten.

Standarden förklarar för att vara säker på att man alltid "tittar" på rätt år, gå efter torsdagen. Den första torsdagen för året är alltid vecka 1, andra torsdagen vecka 2 o.s.v.

1$date = date('Y', strtotime('2019W01-4')); // 4 = Thursday
2// $date = 2019
1$date = date('Y', strtotime('2019W01-4')); // 4 = Thursday
2// $date = 2019

Tack och lov finns också fantastiska bibliotek så som Carbon som underlättar många omvandlingar – så länge som man matar in rätt vecka...


Bonuslänk om ISO 8601: xkcd