Skip to content

Commit bea6e99

Browse files
authored
fix: Fix isoweek implementation - remove user timezone variations (#234)
1 parent 983e85f commit bea6e99

2 files changed

Lines changed: 59 additions & 2 deletions

File tree

expression-src/main/src/interpreter/std-lib/DateAndTimeFunctions.cls

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -519,16 +519,59 @@ public with sharing class DateAndTimeFunctions {
519519
Object dateValue = evaluate(arguments.get(0));
520520
if (!(dateValue instanceof Date)) {
521521
throw new FunctionExecutionException(
522-
'Error executing "ISOWEEK" function: the argument must evaluate to a date value.'
522+
'Error executing "ISOWEEK" function: the argument must evaluate to a date value.'
523523
);
524524
}
525525

526-
return Integer.valueOf(Datetime.newInstanceGmt((Date)dateValue, Time.newInstance(0, 0, 0, 0)).format('w'));
526+
return getIsoWeekNumber((Date) dateValue);
527527
}
528528

529529
public override Arity getArity() {
530530
return Arity.exactly(1);
531531
}
532+
533+
// --- ISO WEEK IMPLEMENTATION ---
534+
535+
private Integer getIsoWeekNumber(Date inputDate) {
536+
Integer isoWeekday = getIsoWeekday(inputDate); // Monday=1 ... Sunday=7
537+
Integer week = floorDiv(inputDate.dayOfYear() - isoWeekday + 10, 7);
538+
539+
if (week < 1) {
540+
return getIsoWeeksInYear(inputDate.year() - 1);
541+
}
542+
543+
Integer weeksInYear = getIsoWeeksInYear(inputDate.year());
544+
if (week > weeksInYear) {
545+
return 1;
546+
}
547+
548+
return week;
549+
}
550+
551+
private Integer getIsoWeeksInYear(Integer year) {
552+
// Dec 28 is always in the last ISO week
553+
Date dec28 = Date.newInstance(year, 12, 28);
554+
Integer isoWeekday = getIsoWeekday(dec28);
555+
return floorDiv(dec28.dayOfYear() - isoWeekday + 10, 7);
556+
}
557+
558+
private Integer getIsoWeekday(Date inputDate) {
559+
// 1900-01-01 was Monday
560+
Integer diff = Date.newInstance(1900, 1, 1).daysBetween(inputDate);
561+
Integer mod = Math.mod(diff, 7);
562+
if (mod < 0) {
563+
mod += 7;
564+
}
565+
return mod + 1;
566+
}
567+
568+
private Integer floorDiv(Integer a, Integer b) {
569+
Integer result = a / b;
570+
if (a < 0 && Math.mod(a, b) != 0) {
571+
result--;
572+
}
573+
return result;
574+
}
532575
}
533576

534577
/**

expression-src/spec/language/std-functions/DateAndTimeFunctionsTest.cls

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,20 @@ private class DateAndTimeFunctionsTest {
8484
private static void isoweekFunctionReturnsTheIsoWeek() {
8585
Assert.areEqual(1, Evaluator.run('ISOWEEK(DATE(2015, 1, 1))'));
8686
Assert.areEqual(52, Evaluator.run('ISOWEEK(DATE(2015, 12, 27))'));
87+
88+
// --- START OF YEAR EDGE CASES ---
89+
Assert.areEqual(53, Evaluator.run('ISOWEEK(DATE(2016, 1, 1))')); // belongs to 2015
90+
Assert.areEqual(1, Evaluator.run('ISOWEEK(DATE(2016, 1, 4))')); // first ISO week
91+
92+
Assert.areEqual(52, Evaluator.run('ISOWEEK(DATE(2017, 1, 1))')); // Sunday → previous year
93+
Assert.areEqual(1, Evaluator.run('ISOWEEK(DATE(2017, 1, 2))')); // Monday → week 1
94+
95+
// --- END OF YEAR EDGE CASES ---
96+
Assert.areEqual(53, Evaluator.run('ISOWEEK(DATE(2015, 12, 31))')); // 53-week year
97+
Assert.areEqual(1, Evaluator.run('ISOWEEK(DATE(2014, 12, 31))')); // 2014-12-31 = Wednesday
98+
99+
Assert.areEqual(1, Evaluator.run('ISOWEEK(DATE(2018, 12, 31))')); // next ISO year
100+
Assert.areEqual(1, Evaluator.run('ISOWEEK(DATE(2019, 12, 30))')); // next ISO year
87101
}
88102

89103
@IsTest

0 commit comments

Comments
 (0)