Skip to content

Instantly share code, notes, and snippets.

@cristiklein
Created April 13, 2024 09:37
Show Gist options
  • Save cristiklein/0790362d08536837a73316600aab671e to your computer and use it in GitHub Desktop.
Save cristiklein/0790362d08536837a73316600aab671e to your computer and use it in GitHub Desktop.
Determine if the school is open on a given Date in Lomma, SE. Works as an App Script in Google Sheets.
/**
* Copy-pasted from here:
* https://lomma.se/utbildning-och-barnomsorg/grundskola/terminer-och-lov.html
*/
const text_from_lomma_se = `;
Vårterminen 2024
2024-01-10 - 2024-06-13
Studiedagar*
2024-01-08 (även fritidshemmet stängt)
2024-01-09
2024-03-13
2024-05-22
2024-06-14 (även fritidshemmet stängt)
Lovdagar
2025-02-19 - 2024-02-23
2024-03-25 - 2024-03-28
2024-05-10
2024-06-07
Höstterminen 2024
2024-08-14 - 2024-12-20
Studiedagar*
2024-08-12 - 2024-08-13 (även fritidshemmet stängt)
2024-10-07 (även fritidshemmet stängt)
2024-10-08
2024-11-26
Lovdagar
2024-10-28 - 2024-11-01
Vårterminen 2025
2025-01-09 - 2025-06-12
Studiedagar*
2025-01-07 (även fritidshemmet stängt)
2025-03-12
2025-05-21
2025-06-13 (även fritidshemmet stängt)
Lovdagar
2025-02-17 - 2025-02-21
2025-04-14 - 2025-04-21
2025-05-29 - 2025-05-30
2025-06-06`
const useCache = true; // set to false during development
/**
* Represents days between (and including) a start and and end date, potentially a single day.
*/
class DateRange {
constructor(start,end=null) {
this.start = new Date(start);
if (end == null) {
end = start;
}
this.end = new Date(end);
}
static parse(jsonDateRange) {
return new DateRange(jsonDateRange.start, jsonDateRange.end);
}
isDateInRange(inputDate) {
if (typeof inputDate == "string")
inputDate = new Date(inputDate);
return (this.start <= inputDate && inputDate <= this.end);
}
}
/**
* Represents a set of DateRange-s
*/
class DateRangeSet {
constructor() {
this.rangeList = [];
}
/**
* Parse a JSON string into a DateRangeSet.
*/
static parse(jsonRangeSet) {
const rangeSet = new DateRangeSet();
for (const jsonDateRange of jsonRangeSet.rangeList) {
rangeSet.add(DateRange.parse(jsonDateRange));
}
return rangeSet;
}
/**
* Add a DateRange to the DateRangeSet
*/
add(dateRange) {
this.rangeList.push(dateRange);
}
/**
* Checks if a date is in a rangeList.
* @param {inputDate} The date to check.
* @returns true if date is in rangeList, otherwise false.
*/
isDateInRange(inputDate) {
for (const range of this.rangeList) {
if (range.isDateInRange(inputDate)) {
return true;
}
}
return false;
}
}
/**
* Convert the text on lomma.se about school holidays into something workable.
* @param {text} Copy-paste text from lomma.se
* @returns A structure containing holidays, days when school is closed and holidays.
*/
function textToSchoolData(text) {
terminer = new DateRangeSet();
stangt = new DateRangeSet();
lovdagar = new DateRangeSet();
let mode = ''
for (const line of text.split('\n')) {
switch (line[0]) {
case 'H':
case 'V':
mode = 'T';
continue;
case 'S':
case 'L':
mode = line[0];
continue;
}
switch (mode) {
case 'T': {
const [ start, end ] = line.split(' - ');
terminer.add(new DateRange(start, end));
break;
}
case 'L': {
const [ start, end ] = line.split(' - ');
lovdagar.add(new DateRange(start, end));
break;
}
case 'S': {
const [ startEnd, comment ] = line.split(' (');
if (comment && comment.includes('stängt')) {
const [ start, end ] = startEnd.split(' - ');
stangt.add(new DateRange(start, end));
}
break;
}
}
}
return { terminer, stangt, lovdagar }
}
/**
* Get school data, if possible from cache
* @returns school data
*/
function getSchoolData() {
const cache = CacheService.getDocumentCache();
let schoolData;
try {
if (useCache) {
/* Parse JSON string */
schoolData = {}
const jsonSchoolData = JSON.parse(cache.get("schoolData"));
for (const rangeName in jsonSchoolData) {
schoolData[rangeName] = DateRangeSet.parse(jsonSchoolData[rangeName]);
}
}
}
catch (e) {
console.log(`Cache error: ${e}`);
}
if (!schoolData || !schoolData.stangt || !schoolData.lovdagar || !schoolData.terminer) {
schoolData = textToSchoolData(text_from_lomma_se);
cache.put("schoolData", JSON.stringify(schoolData));
}
return schoolData;
}
/**
* Checks if parents can work.
* @param {inputDate} The date to check.
* @returns 's' is school is closed (stängt), 'l' is school holiday (lov).
*/
function isSchoolFreakingOpen(inputDate) {
const { terminer, stangt, lovdagar } = getSchoolData();
if (typeof inputDate != "string") {
/* Google Sheets sends us time at midnight **local time**.
* We only really care about the date. */
inputDate.setHours(12);
inputDate = inputDate.toJSON().split('T')[0];
}
if (stangt.isDateInRange(inputDate)) {
return 's';
}
if (!terminer.isDateInRange(inputDate)) {
return 'l';
}
if (lovdagar.isDateInRange(inputDate)) {
return 'l';
}
return '';
}
const schoolData = textToSchoolData(text_from_lomma_se);
function test_isDateInRangeList() {
const testSet = [
[ "2024-06-14", schoolData.stangt, true ],
[ "2024-06-15", schoolData.stangt, false ],
[ "2024-05-10", schoolData.lovdagar, true ],
[ "2024-05-11", schoolData.lovdagar, false ],
];
for (const [ inputDate, rangeList, expected ] of testSet) {
const actual = rangeList.isDateInRange(inputDate);
if (actual != expected) {
throw new Error(`\
AssertionError:
Input: ${inputDate}
Actual: ${actual};
Expected: ${expected}`);
}
console.log(`isDateInRange ${inputDate} ... PASS`);
}
}
function test_isSchoolFreakingOpen() {
const testSet = [
[ "2024-01-08", "s" ],
[ "2024-01-09", "l" ],
[ "2024-01-10", "" ],
[ "2024-05-10", "l" ],
[ "2024-05-11", "" ],
[ new Date("2024-05-10"), "l" ],
[ new Date("2024-05-11"), "" ],
[ new Date("Fri May 10 2024 00:00:00 GMT+0200 (GMT+02:00)"), "l" ],
[ new Date("Sat May 11 2024 00:00:00 GMT+0200 (GMT+02:00)"), "" ],
];
for (const [ inputDate, expected ] of testSet) {
const actual = isSchoolFreakingOpen(inputDate);
if (actual != expected) {
throw new Error(`\
AssertionError:
Input: ${inputDate};
Actual: ${actual};
Expected: ${expected}`);
}
console.log(`isSchoolFreakingOpen ${inputDate} ... PASS`);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment