@@ -50,40 +50,64 @@ class DHIS2Date extends Date {
5050 }
5151}
5252
53- const useServerTimeOffset = ( serverTimezone : string ) : number => {
53+ const calculateOffset = ( inputDate : any , serverTimezone : string ) => {
54+ // we need to assume that the inputDate is in the client time zone due to limitations of javascript logic
55+ // note that this assumption is will be imperfect around daylight savings time changes
56+ const thenClientTime = new Date ( inputDate )
57+ thenClientTime . setMilliseconds ( 0 )
58+
59+ // 'sv' is used for localeString because it is the closest to ISO format
60+ // in principle, any locale should be parsable back to a date, but we encountered an error
61+ // when using en-US in certain environments, which we could not replicate when using 'sv'
62+ // Converting to localeString and then back to date is unfortunately the only current way
63+ // to construct a date that accounts for timezone.
64+ const serverLocaleString = thenClientTime . toLocaleString ( 'sv' , {
65+ timeZone : serverTimezone ,
66+ } )
67+
68+ const thenServerTimeZone = new Date ( serverLocaleString )
69+
70+ return thenClientTime . getTime ( ) - thenServerTimeZone . getTime ( )
71+ }
72+
73+ /**
74+ * Determines if the server/client time zone offset can and should be calculated
75+ * @param {string } serverTimezone string representation of server time zone (Area/Location)
76+ * * @param {string } clientTimezone string representation of client time zone (Area/Location)
77+ * @return {boolean } shouldCalculateOffset
78+ */
79+
80+ const useShouldCalculateOffset = (
81+ serverTimezone : string ,
82+ clientTimezone : string
83+ ) : boolean => {
5484 return useMemo ( ( ) => {
85+ // if client and server time zones are the same, offset is 0 and does not need to be subsequently calculated
86+ if ( serverTimezone === clientTimezone ) {
87+ return false
88+ }
89+ // attempt to calculate current time zone offset, if calcublable: return true; if not calculable, alert and return false
5590 try {
5691 const nowClientTime = new Date ( )
57- nowClientTime . setMilliseconds ( 0 )
58-
59- // 'sv' is used for localeString because it is the closest to ISO format
60- // in principle, any locale should be parsable back to a date, but we encountered an error
61- // when using en-US in certain environments, which we could not replicate when using 'sv'
62- // Converting to localeString and then back to date is unfortunately the only current way
63- // to construct a date that accounts for timezone.
64- const serverLocaleString = nowClientTime . toLocaleString ( 'sv' , {
65- timeZone : serverTimezone ,
66- } )
67- const nowServerTimeZone = new Date ( serverLocaleString )
68- nowServerTimeZone . setMilliseconds ( 0 )
69-
70- return nowClientTime . getTime ( ) - nowServerTimeZone . getTime ( )
92+ calculateOffset ( nowClientTime , serverTimezone )
93+ return true
7194 } catch ( err ) {
7295 console . error (
7396 'Server time offset could not be determined; assuming no client/server difference' ,
7497 err
7598 )
7699 // if date is not constructable with timezone, assume 0 difference between client/server
77- return 0
100+ return false
78101 }
79- } , [ serverTimezone ] )
102+ } , [ serverTimezone , clientTimezone ] )
80103}
81104
82105export const useTimeZoneConversion = ( ) : {
83106 fromServerDate : ( date ?: DateInput ) => DHIS2Date
84107 fromClientDate : ( date ?: DateInput ) => DHIS2Date
85108} => {
86109 const { systemInfo } = useConfig ( )
110+
87111 let serverTimezone : string
88112 const clientTimezone : string =
89113 Intl . DateTimeFormat ( ) . resolvedOptions ( ) . timeZone
@@ -98,35 +122,45 @@ export const useTimeZoneConversion = (): {
98122 )
99123 }
100124
101- const serverOffset = useServerTimeOffset ( serverTimezone )
125+ const shouldCalculateOffset = useShouldCalculateOffset (
126+ serverTimezone ,
127+ clientTimezone
128+ )
102129
103130 const fromServerDate = useCallback (
104131 ( date ) => {
105- const serverDate = new Date ( date )
132+ const jsServerDate = date ? new Date ( date ) : new Date ( Date . now ( ) )
133+ const offset = shouldCalculateOffset
134+ ? calculateOffset ( jsServerDate , serverTimezone )
135+ : 0
106136 const clientDate = new DHIS2Date ( {
107- date : serverDate . getTime ( ) + serverOffset ,
108- serverOffset,
137+ date : jsServerDate . getTime ( ) + offset ,
138+ serverOffset : offset ,
109139 serverTimezone,
110140 clientTimezone,
111141 } )
112142
113143 return clientDate
114144 } ,
115- [ serverOffset , serverTimezone , clientTimezone ]
145+ [ shouldCalculateOffset , serverTimezone , clientTimezone ]
116146 )
117147
118148 const fromClientDate = useCallback (
119149 ( date ) => {
150+ const jsClientDate = date ? new Date ( date ) : new Date ( Date . now ( ) )
151+ const offset = shouldCalculateOffset
152+ ? calculateOffset ( jsClientDate , serverTimezone )
153+ : 0
120154 const clientDate = new DHIS2Date ( {
121155 date,
122- serverOffset,
156+ serverOffset : offset ,
123157 serverTimezone,
124158 clientTimezone,
125159 } )
126160
127161 return clientDate
128162 } ,
129- [ serverOffset , serverTimezone , clientTimezone ]
163+ [ shouldCalculateOffset , serverTimezone , clientTimezone ]
130164 )
131165
132166 return useMemo (
0 commit comments