diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QSession.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QSession.java index 19984e30..4649efbd 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QSession.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QSession.java @@ -53,6 +53,9 @@ public class QSession implements Serializable // implementation-specific custom values private Map values; + public static final String VALUE_KEY_USER_TIMEZONE = "UserTimezone"; + public static final String VALUE_KEY_USER_TIMEZONE_OFFSET_MINUTES = "UserTimezoneOffsetMinutes"; + /******************************************************************************* @@ -453,6 +456,7 @@ public class QSession implements Serializable } + /******************************************************************************* ** Fluent setter for user *******************************************************************************/ @@ -462,5 +466,4 @@ public class QSession implements Serializable return (this); } - } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java index ef24a030..8a65227f 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java @@ -39,8 +39,10 @@ import java.time.temporal.ChronoUnit; import java.util.Calendar; import java.util.List; import java.util.TimeZone; +import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QValueException; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; +import com.kingsrook.qqq.backend.core.model.session.QSession; /******************************************************************************* @@ -674,7 +676,7 @@ public class ValueUtils ////////////////////////////////////////////////////////////////////////////// LocalDateTime givenZonesNow = LocalDateTime.ofInstant(Instant.now(), zone); LocalDateTime startOfDay = givenZonesNow.truncatedTo(ChronoUnit.DAYS); - return (startOfDay.toInstant(zone.getRules().getOffset(computerTime))); + return (startOfDay.toInstant(zone.getRules().getOffset(startOfDay))); } @@ -700,7 +702,7 @@ public class ValueUtils .with(ChronoField.MINUTE_OF_DAY, 0) .with(ChronoField.SECOND_OF_DAY, 0) .with(ChronoField.NANO_OF_DAY, 0); - return (startOfMonth.toInstant(zone.getRules().getOffset(computerTime))); + return (startOfMonth.toInstant(zone.getRules().getOffset(startOfMonth))); } @@ -720,13 +722,13 @@ public class ValueUtils // get date time for now in given zone, truncate it and add offset from utc // ////////////////////////////////////////////////////////////////////////////// LocalDateTime givenZonesNow = LocalDateTime.ofInstant(Instant.now(), zone); - LocalDateTime startOfMonth = givenZonesNow + LocalDateTime startOfYear = givenZonesNow .withDayOfYear(1) .with(ChronoField.HOUR_OF_DAY, 0) .with(ChronoField.MINUTE_OF_DAY, 0) .with(ChronoField.SECOND_OF_DAY, 0) .with(ChronoField.NANO_OF_DAY, 0); - return (startOfMonth.toInstant(zone.getRules().getOffset(computerTime))); + return (startOfYear.toInstant(zone.getRules().getOffset(startOfYear))); } @@ -753,4 +755,28 @@ public class ValueUtils return (null); } + + + /******************************************************************************* + ** Get the (time) zoneId either for the current user session (based on session + ** value UserTimezone or UserTimezoneOffsetMinutes), else the instance's + ** defaultTimeZoneId string. + *******************************************************************************/ + public static ZoneId getSessionOrInstanceZoneId() + { + String timezone = QContext.getQSession().getValue(QSession.VALUE_KEY_USER_TIMEZONE); + if(StringUtils.hasContent(timezone)) + { + return (ZoneId.of(timezone)); + } + + String userTimezoneOffsetMinutesString = QContext.getQSession().getValue(QSession.VALUE_KEY_USER_TIMEZONE_OFFSET_MINUTES); + if(StringUtils.hasContent(userTimezoneOffsetMinutesString)) + { + return (ZoneId.ofOffset("UTC", ZoneOffset.ofTotalSeconds(60 * Integer.parseInt(userTimezoneOffsetMinutesString)))); + } + + return (ZoneId.of(QContext.getQInstance().getDefaultTimeZoneId())); + } + } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java index fdec3ae4..cf7f857f 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java @@ -31,10 +31,13 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; +import java.time.ZoneId; import java.util.Calendar; import java.util.GregorianCalendar; import com.kingsrook.qqq.backend.core.BaseTest; +import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QValueException; +import com.kingsrook.qqq.backend.core.model.session.QSession; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -268,7 +271,7 @@ class ValueUtilsTest extends BaseTest assertEquals("1", ValueUtils.getValueAsType(String.class, 1)); assertEquals(BigDecimal.ONE, ValueUtils.getValueAsType(BigDecimal.class, 1)); assertEquals(true, ValueUtils.getValueAsType(Boolean.class, "true")); - assertArrayEquals("a" .getBytes(StandardCharsets.UTF_8), ValueUtils.getValueAsType(byte[].class, "a")); + assertArrayEquals("a".getBytes(StandardCharsets.UTF_8), ValueUtils.getValueAsType(byte[].class, "a")); assertThrows(QValueException.class, () -> ValueUtils.getValueAsType(Serializable.class, 1)); } @@ -292,4 +295,21 @@ class ValueUtilsTest extends BaseTest } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testGetSessionOrInstanceZoneId() + { + assertEquals(ZoneId.of("UTC"), ValueUtils.getSessionOrInstanceZoneId()); + + QContext.getQInstance().setDefaultTimeZoneId("America/Chicago"); + assertEquals(ZoneId.of("America/Chicago"), ValueUtils.getSessionOrInstanceZoneId()); + + QContext.getQSession().setValue(QSession.VALUE_KEY_USER_TIMEZONE_OFFSET_MINUTES, "-300"); + assertEquals(ZoneId.of("UTC-05:00"), ValueUtils.getSessionOrInstanceZoneId()); + } + } \ No newline at end of file diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java index 5bc7823f..52595b00 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java @@ -447,7 +447,8 @@ public class QJavalinImplementation context.cookie(SESSION_ID_COOKIE_NAME, session.getIdReference(), SESSION_COOKIE_AGE); } - setUserTimezoneOffsetMinutesHeaderInSession(context, session); + setUserTimezoneOffsetMinutesInSession(context, session); + setUserTimezoneInSession(context, session); } catch(QAuthenticationException qae) { @@ -493,7 +494,7 @@ public class QJavalinImplementation /******************************************************************************* ** *******************************************************************************/ - private static void setUserTimezoneOffsetMinutesHeaderInSession(Context context, QSession session) + private static void setUserTimezoneOffsetMinutesInSession(Context context, QSession session) { String userTimezoneOffsetMinutes = context.header("X-QQQ-UserTimezoneOffsetMinutes"); if(StringUtils.hasContent(userTimezoneOffsetMinutes)) @@ -503,7 +504,7 @@ public class QJavalinImplementation /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // even though we're putting it in the session as a string, go through parse int, to make sure it's a valid int. // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - session.setValue("UserTimezoneOffsetMinutes", String.valueOf(Integer.parseInt(userTimezoneOffsetMinutes))); + session.setValue(QSession.VALUE_KEY_USER_TIMEZONE_OFFSET_MINUTES, String.valueOf(Integer.parseInt(userTimezoneOffsetMinutes))); } catch(Exception e) { @@ -514,6 +515,20 @@ public class QJavalinImplementation + /******************************************************************************* + ** + *******************************************************************************/ + private static void setUserTimezoneInSession(Context context, QSession session) + { + String userTimezone = context.header("X-QQQ-UserTimezone"); + if(StringUtils.hasContent(userTimezone)) + { + session.setValue(QSession.VALUE_KEY_USER_TIMEZONE, userTimezone); + } + } + + + /******************************************************************************* ** *******************************************************************************/