/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
* This source file is part of the Cangjie project, licensed under Apache-2.0
* with Runtime Library Exception.
*
* See https://cangjie-lang.cn/pages/LICENSE for license information.
*/
/*
* Gets the local time zone.
* Load timezone which name is equal to the value of the TZ environment variable.
* Get tzdata path from the value of the CJ_TZPATH environment variable.
* If CJ_TZPATH is not set, use the data /etc/zoneinfo in ohos or /usr/share/zoneinfo in linux as the default path.
* If the value of the TZ environment variable is empty or does not exist, load the local time zone from the system.
*/
package std.time
import std.convert.*
import std.collection.ArrayList
@FastNative
foreign func CJ_TIME_GetLocalTimeOffset(): Int64
@When[os != "Windows"]
@FastNative
foreign func CJ_TIME_GetRealPath(path: CString): CString
foreign func CJ_TIME_GetLocalTimeZoneProperty(): CString
foreign func CJ_TIME_GetTzDataById(id: CString, dataLen: CPointer<Int64>): CPointer<UInt8>
func getOhosTzDataById(id: String): ?Array<Byte> {
let cstr = unsafe { LibC.mallocCString(id) }
let bytesOp = getOhosTzDataById(cstr)
unsafe { LibC.free(cstr) }
bytesOp
}
func getOhosTzDataById(id: CString): ?Array<Byte> {
var len = 0
let dataptr = unsafe { CJ_TIME_GetTzDataById(id, inout len) }
if (dataptr.isNull()) {
return None
} else {
let data = Array<Byte>(len) {
i => unsafe { dataptr.read(i) }
}
unsafe { LibC.free(dataptr) }
return data
}
}
func initOhosDefault(): ?TimeZone {
let id = unsafe { CJ_TIME_GetLocalTimeZoneProperty() }
if (id.isNull()) {
return None
}
try {
return if (let Some(tzdata) <- getOhosTzDataById(id)) {
TimeZone.loadFromTZData(id.toString(), tzdata)
} else {
None
}
} finally {
unsafe { LibC.free(id) }
}
}
@When[os == "Windows"]
func initLocal(): TimeZone {
var timeZ = unsafe { LibC.mallocCString("TZ") }
let ctz = unsafe { CJ_TIME_GetEnvVariable(timeZ) }
unsafe { LibC.free(timeZ) }
if (!ctz.isNull() && !ctz.isEmpty()) {
let env = unsafe { LibC.mallocCString(TZPATH) }
let envPath = unsafe { CJ_TIME_GetEnvVariable(env) }
unsafe { LibC.free(env) }
if (!envPath.isNull() && !envPath.isEmpty()) {
var id = ctz.toString()
let envPathArray = envPath.toString().split(SEPARATOR, removeEmpty: true)
try {
id = id.replace("\\", "/")
let local = TimeZone.loadFromPaths(id, envPathArray)
return local
} catch (_) {
// go on
}
}
}
initLocalDefault()
}
@When[os != "Windows" && os != "iOS"]
func initLocal(): TimeZone {
var timeZ = unsafe { LibC.mallocCString("TZ") }
let ctz = unsafe { CJ_TIME_GetEnvVariable(timeZ) }
unsafe { LibC.free(timeZ) }
if (!ctz.isNull() && !ctz.isEmpty()) {
// get timezone from system by id TZ
let id = ctz.toString()
var envPathArray = ArrayList<String>(TZIF_SOURCE)
let env = unsafe { LibC.mallocCString(TZPATH) }
let envPath = unsafe { CJ_TIME_GetEnvVariable(env) }
unsafe { LibC.free(env) }
if (!envPath.isNull() && !envPath.isEmpty()) {
envPathArray.add(all: envPath.toString().split(SEPARATOR, removeEmpty: true), at: 0)
}
try {
let local = TimeZone.loadFromPaths(id, unsafe { envPathArray.getRawArray()[0..envPathArray.size] })
return local
} catch (_) {
// go on
}
}
// if not set TZ or get TimeZone from TZ failed, get local time zone from system
let clocalpath = unsafe { LibC.mallocCString(LOCALTIME_SRC) }
let realpath = unsafe { CJ_TIME_GetRealPath(clocalpath) }
unsafe { LibC.free(clocalpath) }
if (!realpath.isNull()) {
try {
let path = realpath.toString()
let name = getTimeZoneName(path)
let tzdata = loadTZifData(path)
return TimeZone.loadFromTZData(name, tzdata)
} catch (_) {
// go on
} finally {
unsafe { LibC.free(realpath) }
}
}
// try get ohos timezone first
initOhosDefault() ?? initLocalDefault()
}
@When[os == "iOS"]
func initLocal(): TimeZone {
initLocalDefault()
}
// Get timezone which name is UTC+Offset
func initLocalDefault(): TimeZone {
let offset: Int64 = unsafe { CJ_TIME_GetLocalTimeOffset() }
if (offset == 0) {
return TimeZone.UTC
}
let duration = Duration.minute * offset
let hour = duration.toHours()
let minute = duration.abs().toMinutes() % 60
let cname: CString = unsafe { CJ_TIME_GetLocalTimeZoneProperty() }
let name = if (cname.isNull()) {
"UTC${hour.format("+.2")}:${minute.format(".2")}"
} else {
let str = cname.toString() // Always valid utf8 sequence
unsafe { LibC.free(cname) }
str
}
return TimeZone(name, duration)
}
/**
* Try to get timezone name by the path of tzdata
* for unix like system
* prefix of ubuntu is: /usr/share/zoneinfo/
* prefix of ohos is : /system/etc/zoneinfo/
* prefix of macos is : /var/db/timezone/zoneinfo.default/ or others
*/
@When[os != "Windows"]
func getTimeZoneName(path: String): String {
if (let Some(pos) <- path.lastIndexOf("zoneinfo")) {
var substring = path[(pos + 8)..path.size] // 8 is the size of "zoneinfo"
if (let Some(pos) <- substring.indexOf("/")) {
return substring[pos + 1..]
}
}
return "Local"
}