How to get user timezone/time locale information?

@quix

Did quick examine and used Mri to get reference for LocalDateTime and DateTime - Here are the methods:


https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1i18n_1_1XCalendar4.html#affc018d1cc480f7d136a43f691d62338


https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1i18n_1_1XCalendar.html#ac31e3b61dd515c2ebb983f55f09be2a7


You can also get each.
.

I have nothing further as I only wanted to provide some links to info I had.

@Ratslinger
When I do oMRI.inspect(theCalendar.LocalDateTime) I just get the return value of that function. How are you examining and getting the reference? Are you selecting the property/function and getting generated code snippet or double clicking the property/function? When I double click I get the return value (if theCalendar.loadDefaultCalendar(theLocale) is called; nothing seems to happen if it’s not). The generated snippet (if theCalendar.loadDefaultCalendar(theLocale) is called) just shows LocalDateTime or DateTime respectively assigned to getLocalDateTime or getDateTime and neither of the functions you linked to.

@quix

I load the Mri library then inspect after the item is declared (or elsewhere as may need):

Mri theCalendar

On property, right mouse & select IDL ref - I have my MRI set for LO.
.
Some Mri info here → Home · hanya/MRI Wiki · GitHub

@Lupp
I took what you posted and changed it for my needs so that UTC offset is standard and allows two digit hours while also ensuring the offset is properly rounded. Here it is for anyone who wants to ensure timezone offset correctness with this forsaken language. I am interested in critique, if this could be improved, if somebody gets an incorrectly calculated or rounded timezone, etc… This assumes that timezone offsets will have 15 minute offsets and more explanation in the comments.

REM  *****  BASIC  *****

Option Explicit

Function localDateTimeOffset(pMode As Integer)
Dim utcPrefix As String
If (pMode = 0) Then
	utcPrefix = ""
ElseIf (pMode = 1) Then
	utcPrefix = " UTC"
Else
	MsgBox("localDateTimeOffset requires 1 for UTC prefix string or 0 for none")
	Stop
EndIf
Dim aLocale As New com.sun.star.lang.Locale
Dim oLocaleCalendar As Object
Dim offset As Double
Dim fixedOffset As Double
Dim result
Dim signChar

oLocaleCalendar = CreateUnoService("com.sun.star.i18n.LocaleCalendar")
With oLocaleCalendar 
 .loadDefaultCalendar(aLocale) REM Uses Unix NullDate!
 '.LocalDateTime is time local to user. .DateTime is UTC standard time. 
 'Formatting these two will see this. These are not documented functions. 
 'May cause rounding issues if second function lags too much
 offset = .LocalDateTime - .DateTime
End With
'Some tests you can run by uncommenting and respective expected results
'offset = -0.332916666667 '7.99/24 -8:00
'offset = -0.304166666667 '7.3/24 -7:30
'offset = -0.295833333333 '7.1/24 -7:15
'offset = -0.291666666667 '7.0/24 -7:00
'offset = -0.3125 '7.5/24 -7:30
'offset = -0.520833333333 '12.5/24 -12:30
'offset = 0.691666666667 '16.6/24 +16:45:
'offset = -0.5 '12.0/24 -12:00
'offset = 0 '0.0/24 +00:00

signChar = IIf(sgn(offset)<0,"-", "+") 'Get sign indicating offset is behind or ahead of UTC to prepend to output

'Format and other functions round number so need to make sure time is properly rounded. 
'Assuming timezone works in 15 minute increments (that's what the UTC offset wikipedia article says) and the datetime functions were designed with 
'that assumptiom, this will make sure the offset is rounded to the intended 15 minute increment.
fixedOffset = fracCeiling(offset * 24, 15) 

'HH:MM is not used because it rounds and/or doesn't round when it shouldn't
result = utcPrefix & signChar & Replace(Format(fixedOffset, "00.00"), ".", ":")

localDateTimeOffset = result
End Function

Function fracCeiling(offset As Double, increment As Double) As Double
	Dim oService As Object
	Dim offsetAbs As Double
	Dim integerPortion As Double
	Dim mantissaLowRes As Double
	Dim mantissa As Double
	Dim offsetRounded As Double
	Dim mantissaRounded As Double
	Dim result As Double
	
	If (increment * 100 = 0) Then
		MsgBox("Divide by 0 error! Time multiple can't be 0!")
		Stop	
	EndIf
	
	offsetAbs = Abs(offset) 'Make sure offset is positive
	integerPortion = Fix(offsetAbs) 'Remove mantissa from offset

	mantissaLowRes = CDbl(Format(offsetAbs, ".000")) 'Reduce precision of offset to stop unnecessary rounding

	mantissa = mantissaLowRes - integerPortion 'Get the mantissa from the offset
	
	oService = createUnoService("com.sun.star.sheet.FunctionAccess")
				
	offsetRounded = oService.CallFunction("ISO.CEILING", Array(offsetAbs, increment / 60 )) 'Make sure the hour is rounded correctly
	mantissaRounded = (oService.CallFunction("ISO.CEILING", Array(mantissa, increment / 60)) * 60) / 100 'Round the mantissa separately so it can be converted to minutes
	if (mantissaRounded = .6) Then 'So that offset at ?:00 isn't ?:60
		mantissaRounded = 0
	EndIf

	result = Fix(offsetRounded) + mantissaRounded 'Add rounded offset and mantissa back together
	fracCeiling=result
End Function

Sub TestFunction
	MsgBox(localDateTimeOffset(1))
End Sub

.
Remaining question about the API:
Why does creating UnoService com.sun.star.i18n.LocaleCalendar also create/import com.sun.star.i18n.LocaleCalendar2 and inherit functions from it as indicated by MRI and testing inherited functions myself?

  1. Note that the two operations getting current time (.LocalDateTime and .DateTime) can produce slightly different results (without taking TZ offset into account; naturally, because they execute at different moments, one after the other); and so, even when your TZ co-incides with UTC (say, you are in GMT outside of DST), the difference may be non-zero (and, as far as I remember, there is no specification in which order the subexpressions are calculated; so either of them cauld be calculated first, and thus the sigh after subtraction could be either + or -). Thus, getting the sigh from unrounded offset is unreliable.

  2. Why ceiling? Again, due to the floating-point limited-precision operation, and unspecified order of subexpression evaluation, ceiling could happen to incorrectly round to the next multiple.

  3. Converting the number to text and back to number, and calling Calc functions for rounding, is slow. Taking into account that Basic floating-point-to-integer conversions are rounding, I would recommend this function, that returns number of minutes rounded to given multiple (keeping the sign, so you may use its sign after normalization):

Function RoundToNMinutes(offset As Double, n As Integer) As Integer
  RoundToNMinutes = CLng(offset * (24 * 60) / n) * n
End Function

Indeed, wall clock time doesn’t round, because 23:59:59.999 is still not midnight yet. Since offsets are durations, you need duration number format: [HH]:MM, note the square brackets. They round the times properly to the nearest, as you need, compensating the minuscule floating-point errors.

fixedOffset = RoundToNMinutes(offset, 15)
result = utcPrefix & Format(fixedOffset / 1440, "+[HH]:MM;-[HH]:MM")

Because creating any service means to LibreOffice: “create the object that registered itself as implementing this service”. And indeed, that object can (and usually does) implement many more other services. E.g., we don’t implement objects solely for legacy interfaces; since css::i18n::LocaleCalendar2 includes all interfaces included into css::i18n::LocaleCalendar, and the latter does not have own properties, any implementation of the former will naturally implement the latter.

1 Like

@mikekaganski
Thank you for the response.

  1. Would assigning the outputs of each to a variable fix the ordering issue (may add a delay, not sure if possible to measure or account for it)?
  2. I am under the impression valid timezone offsets are multiples of 15 minutes. The idea was to not only jump to the nearest multiple of 15 to satisfy the idea that a valid timezone was a multiple of 15, but if there was any imprecision from rounding or other there was a 15 multiple worth of slack. But I see that the formatting code you provided most likely covers all the cases for the rounding errors and I’m also realizing that the timezone shouldn’t be forced even if it’s supposed to be multiple of 15.
  3. Why do you divide and multiply by n? I get the same result multiplying offset by (24 * 60). Also for offsets like 0.691666666667 it outputs +16:36 rather than +16:45, but as said in 2 this is likely a non-issue.
  4. Where is that bracket functionality documented (I see the part about separating with semicolon, I’m not talking about that)? Format function documentation (Format Function) says:

- + $ ( ) space: A plus (+), minus (-), dollar ($), space, or brackets entered directly in the format code is displayed as a literal character.

Indeed, but why? A code that uses rounding-to-nearest instead of ceiling would be robust and independent of calculation order.

I divide by n and then convert to Long using CLng, which implicitly rounds; and the result (the integer!) is then multiplied by n, thus getting the correctly rounded-to-multiple value. And no, it doesn’t output “+16:36” for 0.691666666667, but +16:30 (if my code is used), which it should; no, it shouldn’t give “+16:45”, because offsets like 0.691666666667 are unexpected, and the time differences will differ by a tiny fraction of a second; so rounding-to-nearest for “16:30:00.013” or for “16:29:59.983” is the expected result; and for the former, rounding to “+16:45” would be wrong.

Exactly the Format function help has the reference to detailed Number Format Codes documentation; and the latter has a section for “Time Formats” discussing this. No, there is no discussion about rounding differences; we should add it there.

The sign is used to determine if offset is behind or ahead of UTC so order is important for this.

Appreciate the answers and clarification for the other points.

This reply has nothing to do with my question. Rounding preserves the sign.