nudge icon indicating copy to clipboard operation
nudge copied to clipboard

UTC vs Local Time

Open boberito opened this issue 1 year ago • 39 comments

Nudge uses UTC time which can make timing of things challenging when there are multiple offices across different time zones.

You either have to set the time when it isn't convenient for some or multiple profiles have to be set.

  1. Is there a way to use local time?
  2. What was the reasoning behind UTC?
  3. Any interest in a PR for the ability to use local time?

boberito avatar Mar 16 '23 16:03 boberito

https://github.com/macadmins/nudge/issues/437 https://github.com/macadmins/nudge/issues/386

aysiu avatar Mar 16 '23 16:03 aysiu

  1. No
  2. I prefer UTC/lazy when dealing with singular events. "multiple time zones" was out of scope at the time but we have other ways of handling this. Also I copied munki's method.
  3. Absolutely! I would prefer us to discuss what this looks like before you implement it.

erikng avatar Mar 16 '23 18:03 erikng

I fully endorse the lazy when dealing with dates and times and zones and nonsense and copying munkis method is also good.

Slack good place?

boberito avatar Mar 16 '23 19:03 boberito

We can do it here if you don't mind.

erikng avatar Mar 16 '23 20:03 erikng

So I was thinking a config option under optionalFeatures like useLocalTime so people can opt in.

I see the line let dateFormat = "yyyy-MM-dd HH:mm:ss Z" and dateFormatterISO8601.timeZone = TimeZone(identifier: "UTC") in 2 places in Util.swift

I'm guessing those are the key. It may be as simple as throwing an if statement around that area, and if you want to use local time have system determine the local time zone and use that in the date format.

Thoughts?

boberito avatar Mar 17 '23 16:03 boberito

Not sure how difficult it would be to port to Nudge, but I think these are the relevant bits for how Munki handles things: https://github.com/munki/munki/blob/4e35f56b9c19d3dff3dc1bd85c634cbc1b507938/code/apps/Managed%20Software%20Center/Managed%20Software%20Center/MunkiItems.swift#L958-L959 https://github.com/munki/munki/blob/4e35f56b9c19d3dff3dc1bd85c634cbc1b507938/code/apps/Managed%20Software%20Center/Managed%20Software%20Center/munki.swift#L300-L308

aysiu avatar Mar 17 '23 17:03 aysiu

What's unclear to me is what is the expected date an admin puts in? They can't put UTC now, so it needs to be a new date format.

Or are we saying continue to use the UTC date format but if an admin says 08:00:00 it means 8am locally and 20:00:00 means 8pm locally? If so that makes sense to me but may be confusing to others.

erikng avatar Mar 17 '23 19:03 erikng

Yes! Completely that exactly.

boberito avatar Mar 17 '23 20:03 boberito

I’m not sure how to not be confusing though. But if the admin enables the setting useLocalTime then they kind of should know what they’re getting into?

A thought could be change the default to always have the Z at the end and really stress it’s UTC. So the admin doesn’t have to enter it.

Or go the opposite, local time becomes the default and UTC is a choice.

boberito avatar Mar 17 '23 20:03 boberito

I believe, based the Munki code I posted above, that Munki just chops the z off the end, and then assumes local time. It's still in the XML, but the Munki documentation says local time is used.

aysiu avatar Mar 17 '23 20:03 aysiu

I think keeping the time format identically as it is now and then have an optional key makes the most sense. It will be the least amount of code changes.

erikng avatar Mar 17 '23 22:03 erikng

That’s probably very true. I’ll see if I can whip something together this weekend.

boberito avatar Mar 18 '23 00:03 boberito

Multinational companies might want to give their users the same deadline in their own local time, to be fair to all. We have employees that wrap the globe from Hawaii to Sydney. If we send an email announcing that users have "until 5pm Friday to apply this update," then the statement should be true for anyone.

bradtchapman avatar Mar 18 '23 03:03 bradtchapman

Yes that's what he's proposing. I'm just trying to ensure we don't add a ton of complexity because time is used in other locations.

erikng avatar Mar 18 '23 03:03 erikng

Well that was unexpected. Since a date in a configuration profile is an NSDate, it requires something like a Z at the end to be valid. Unless that's changed to a string, this idea may be dead.

boberito avatar Mar 18 '23 17:03 boberito

Technically it accepts strings in the mobileconfig because JAMF does some really silly things but I've never been too public with that.

That said, why can't we keep the format and just convert to the local timezone?

erikng avatar Mar 18 '23 17:03 erikng

I figured it would probably accept a string.

So if useLocalTime is set, it ignores the Z?

boberito avatar Mar 18 '23 17:03 boberito

Another stumbling point. Swift only includes these TimeZone abbreviations. And well...there's quite a few more.

"BST": "Europe/London",
"UTC": "UTC",
"CAT": "Africa/Harare",
"WIT": "Asia/Jakarta",
"IST": "Asia/Kolkata",
"PET": "America/Lima",
"NZST": "Pacific/Auckland",
"AKST": "America/Juneau",
"MDT": "America/Denver",
"CST": "America/Chicago",
"ICT": "Asia/Bangkok",
"PST": "America/Los_Angeles",
"BRST": "America/Sao_Paulo",
"EAT": "Africa/Addis_Ababa",
"CEST": "Europe/Paris",
"NST": "America/St_Johns",
"WEST": "Europe/Lisbon",
"NDT": "America/St_Johns",
"PHT": "Asia/Manila",
"COT": "America/Bogota",
"EET": "Europe/Athens",
"MSK": "Europe/Moscow",
"JST": "Asia/Tokyo",
"ADT": "America/Halifax",
"TRT": "Europe/Istanbul",
"GST": "Asia/Dubai",
"NZDT": "Pacific/Auckland",
"CLT": "America/Santiago",
"CDT": "America/Chicago",
"CET": "Europe/Paris",
"BRT": "America/Sao_Paulo",
"EDT": "America/New_York",
"SGT": "Asia/Singapore",
"HST": "Pacific/Honolulu",
"EST": "America/New_York",
"WAT": "Africa/Lagos",
"HKT": "Asia/Hong_Kong",
"KST": "Asia/Seoul",
"CLST": "America/Santiago",
"GMT": "GMT",
"PDT": "America/Los_Angeles",
"BDT": "Asia/Dhaka",
"AKDT": "America/Juneau",
"MST": "America/Phoenix",
"IRST": "Asia/Tehran",
"WET": "Europe/Lisbon",
"AST": "America/Halifax",
"MSD": "Europe/Moscow",
"PKT": "Asia/Karachi",
"ART": "America/Argentina/Buenos_Aires",
"EEST": "Europe/Athens"

This would cover most major areas I believe, but using my obscure test..the country of Nauru is Pacific/Nauru and the abbreviation is NRT but swift doesn't include it. When I set my location to that in System Settings, it identifies as that timezone(Pacific/Nauru). And it looks like the abbreviation is needed for some ISO 8601 conversions.

boberito avatar Mar 18 '23 18:03 boberito

Yay swift lol. This is why I hate dealing with dates and times.

So if useLocalTime is set, it ignores the Z?

That's what I would do to reduce code complexity. It then becomes a literal time to the local time in 24 hour time format.

erikng avatar Mar 18 '23 19:03 erikng

How does this affect your deferrals? It's still adding +launchdinterval, +3600, or +86400 to the last time the button was clicked, regardless of local time, correct?

And the requiredInstallationDate should not care about the UTC vs local time, it just needs to know "what time is this due."

Is that right?

bradtchapman avatar Mar 18 '23 19:03 bradtchapman

Yes. IMO all this should impact is how the requiredInstallationDate is processed and normalized to the local time zone. All other logic should stay intact so as not to break everything else.

erikng avatar Mar 18 '23 20:03 erikng

@erikng trying to figure out either a way to query some random thing like even Wikipedia to get that list live or even just get that actual list and make an include file of the couple hundred time zone abbreviations

boberito avatar Mar 18 '23 20:03 boberito

Neither sound ideal. I don't want nudge calling out to endpoints outside of our control and a hardcoded list seems non ideal to me. Perhaps you are overthinking this.

Take the UTC time as a literal time and compare it against the local time in a 24 hour format. I'm not sure why you want to convert the UTC at all but perhaps I'm missing something you've discovered.

erikng avatar Mar 18 '23 20:03 erikng

So Swift doesn't check the definitions at /usr/share/zoneinfo ?

I realize not having Nauru is kind of an edge case, but does Swift fallback to another nearby location / time zone if you are in Nauru? Can you spoof your location? Or change it in System Preferences to simulate it?

bradtchapman avatar Mar 18 '23 20:03 bradtchapman

@bradtchapman If you do a swift playground, change your location to Nauru in System Settings

let timeZone = TimeZone.current.identifier

print("Current: \(timeZone)")
let TZAD = TimeZone.abbreviationDictionary
let timeZoneIdentifiers = TimeZone.knownTimeZoneIdentifiers
print(timeZoneIdentifiers)

This prints out Current: Pacific/Nauru - you'll see Pacific/Nauru isnt listed. Nauru might be an edge case, but I'm sure there's less edgy cases not covered. Maybe UTC is the choice for them.

/var/db/timezone/tz/ and /usr/share/zoneinfo seem to have useful info, but it maybe looks more like the UTC offset - I'll have to dig deeper.

@erikng in order to convert UTC to local, you gotta do a dateFormatter.timeZone = TimeZone.current - but maybe I'm over complicating by converting back using ISO8601DateFormatter()

More keyboard banging will commence later on.

boberito avatar Mar 18 '23 22:03 boberito

import Cocoa

let timeZone = TimeZone.current.identifier
print("Current: \(timeZone)")
let local = TimeZone.current.identifier
let localDate = Date()
print("UTC Time--",localDate)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")

var myDate = dateFormatter.string(from: localDate)
var convertedLocalTime = ""

if let dt = dateFormatter.date(from: myDate) {
    dateFormatter.timeZone = TimeZone.current
    dateFormatter.dateFormat = "yyyy-MM-dd H:mm:ss Z"
    convertedLocalTime = dateFormatter.string(from: dt)
} else {
    print("There was an error decoding the string")
}

print("convertedLocalTime String--",convertedLocalTime)
let isodateFormatter = ISO8601DateFormatter()
isodateFormatter.timeZone = TimeZone(identifier: local)
isodateFormatter.formatOptions = [.withFullDate, .withDashSeparatorInDate,.withSpaceBetweenDateAndTime,.withFullTime,.withColonSeparatorInTime]
let date = isodateFormatter.date(from:convertedLocalTime)
print("convertedLocalTime ISO--",date!)

Doing this in a playground with my local time results in

Current: America/New_York
UTC Time-- 2023-03-19 01:53:45 +0000
convertedLocalTime String-- 2023-03-18 21:53:45 -0400
convertedLocalTime ISO-- 2023-03-19 01:53:45 +0000

I believe things need to go back to an ISO formatted date.

According to https://developer.apple.com/documentation/foundation/dateformatter - Apple recommends

In macOS 10.12 and later or iOS 10 and later, use the ISO8601DateFormatter class when working with ISO 8601 date representations.

But as you can see convertedLocalTime ISO is not correct. I'm guessing it's due to the wrong format somehow.

boberito avatar Mar 19 '23 02:03 boberito

Now try using different calendars other than Gregorian and watch the world blow up. :)

erikng avatar Mar 19 '23 02:03 erikng

"The requiredInstallationDate field supports digits and dashes only, and all deadlines must conform to the Gregorian calendar. Dates expressed using the Hebrew, Indian, Muslim, Chinese, or other lunar or quasi-lunar calendars will not be processed and Nudge will not launch."

You can't accommodate everyone. You'll make yourself nuts.

—Sent from my iPhone

On Mar 18, 2023, at 7:11 PM, Erik Gomez @.***> wrote:

 Now try using different calendars other than Gregorian and watch the world blow up. :)

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.

bradtchapman avatar Mar 19 '23 03:03 bradtchapman

Maybe this is dead until people much smarter than me can figure it out

import Cocoa

let localISOFormatter = ISO8601DateFormatter()
localISOFormatter.timeZone = TimeZone.current
let ISOstring = localISOFormatter.string(from: Date())
print("ISO String--",ISOstring)
let ISODate = localISOFormatter.date(from: ISOstring)
print("ISO Date--",ISODate!)

Outputs

ISO String-- 2023-03-19T14:41:42-04:00
ISO Date-- 2023-03-19 18:41:42 +0000

So maybe Date type is always UTC? 🤷🏻🤷🏻

boberito avatar Mar 19 '23 18:03 boberito

I think I was going down the wrong path. I was trying to convert in Utils() the getCurrentDate and the other to nonUTC

But another of the problem is after doing research and talking to some people. Date type is really a double that’s EPOCH.

So unless the config profile was changed to a String (which you said it does accept) you can’t have a Date type in a configuration profile that’s not UTC. It’s just how it is.

I’m wondering if this may sort of already work in a way to use local time since you’ve done the hard work to allow it to accept a string that gets converted to a date.

More testing to occur tomorrow.

Apologies for the rambling in the Issue ticket, it’s been helpful as sort of notes. And it may be helpful for others to see how confusing a “simple” change could actually be.

boberito avatar Mar 20 '23 01:03 boberito