Skip to content

Instantly share code, notes, and snippets.

@hikaMaeng
Created August 24, 2025 07:51
Show Gist options
  • Save hikaMaeng/bc41eeb35e6e71e5d472b81637b27d33 to your computer and use it in GitHub Desktop.
Save hikaMaeng/bc41eeb35e6e71e5d472b81637b27d33 to your computer and use it in GitHub Desktop.
package kore.time
enum class CountryCode(val code:String, val fullName:String) {
AD("AD", "Andorra"),
AE("AE", "United Arab Emirates"),
AF("AF", "Afghanistan"),
AL("AL", "Albania"),
AM("AM", "Armenia"),
AQ("AQ", "Antarctica"),
AR("AR", "Argentina"),
AS("AS", "Samoa (American)"),
AT("AT", "Austria"),
AU("AU", "Australia"),
AZ("AZ", "Azerbaijan"),
BB("BB", "Barbados"),
BD("BD", "Bangladesh"),
BE("BE", "Belgium"),
BG("BG", "Bulgaria"),
BM("BM", "Bermuda"),
BO("BO", "Bolivia"),
BR("BR", "Brazil"),
BT("BT", "Bhutan"),
BY("BY", "Belarus"),
BZ("BZ", "Belize"),
CA("CA", "Canada"),
CH("CH", "Switzerland"),
CI("CI", "Côte d'Ivoire"),
CK("CK", "Cook Islands"),
CL("CL", "Chile"),
CN("CN", "China"),
CO("CO", "Colombia"),
CR("CR", "Costa Rica"),
CU("CU", "Cuba"),
CV("CV", "Cape Verde"),
CY("CY", "Cyprus"),
CZ("CZ", "Czech Republic"),
DE("DE", "Germany"),
DO("DO", "Dominican Republic"),
DZ("DZ", "Algeria"),
EC("EC", "Ecuador"),
EE("EE", "Estonia"),
EG("EG", "Egypt"),
EH("EH", "Western Sahara"),
ES("ES", "Spain"),
FI("FI", "Finland"),
FJ("FJ", "Fiji"),
FK("FK", "Falkland Islands"),
FM("FM", "Micronesia"),
FO("FO", "Faroe Islands"),
FR("FR", "France"),
GB("GB", "Britain (UK)"),
GE("GE", "Georgia"),
GF("GF", "French Guiana"),
GI("GI", "Gibraltar"),
GL("GL", "Greenland"),
GR("GR", "Greece"),
GS("GS", "South Georgia & the South Sandwich Islands"),
GT("GT", "Guatemala"),
GU("GU", "Guam"),
GW("GW", "Guinea-Bissau"),
GY("GY", "Guyana"),
HK("HK", "Hong Kong"),
HN("HN", "Honduras"),
HT("HT", "Haiti"),
HU("HU", "Hungary"),
ID("ID", "Indonesia"),
IE("IE", "Ireland"),
IL("IL", "Israel"),
IN("IN", "India"),
IO("IO", "British Indian Ocean Territory"),
IQ("IQ", "Iraq"),
IR("IR", "Iran"),
IT("IT", "Italy"),
JM("JM", "Jamaica"),
JO("JO", "Jordan"),
JP("JP", "Japan"),
KE("KE", "Kenya"),
KG("KG", "Kyrgyzstan"),
KI("KI", "Kiribati"),
KP("KP", "Korea (North)"),
KR("KR", "Korea (South)"),
KZ("KZ", "Kazakhstan"),
LB("LB", "Lebanon"),
LK("LK", "Sri Lanka"),
LR("LR", "Liberia"),
LT("LT", "Lithuania"),
LV("LV", "Latvia"),
LY("LY", "Libya"),
MA("MA", "Morocco"),
MD("MD", "Moldova"),
MH("MH", "Marshall Islands"),
MM("MM", "Myanmar (Burma)"),
MN("MN", "Mongolia"),
MO("MO", "Macau"),
MQ("MQ", "Martinique"),
MT("MT", "Malta"),
MU("MU", "Mauritius"),
MV("MV", "Maldives"),
MX("MX", "Mexico"),
MY("MY", "Malaysia"),
MZ("MZ", "Mozambique"),
NA("NA", "Namibia"),
NC("NC", "New Caledonia"),
NF("NF", "Norfolk Island"),
NG("NG", "Nigeria"),
NI("NI", "Nicaragua"),
NP("NP", "Nepal"),
NR("NR", "Nauru"),
NU("NU", "Niue"),
NZ("NZ", "New Zealand"),
PA("PA", "Panama"),
PE("PE", "Peru"),
PF("PF", "French Polynesia"),
PG("PG", "Papua New Guinea"),
PH("PH", "Philippines"),
PK("PK", "Pakistan"),
PL("PL", "Poland"),
PM("PM", "St Pierre & Miquelon"),
PN("PN", "Pitcairn"),
PR("PR", "Puerto Rico"),
PS("PS", "Palestine"),
PT("PT", "Portugal"),
PW("PW", "Palau"),
PY("PY", "Paraguay"),
QA("QA", "Qatar"),
RO("RO", "Romania"),
RS("RS", "Serbia"),
RU("RU", "Russia"),
SA("SA", "Saudi Arabia"),
SB("SB", "Solomon Islands"),
SD("SD", "Sudan"),
SG("SG", "Singapore"),
SR("SR", "Suriname"),
SS("SS", "South Sudan"),
ST("ST", "Sao Tome & Principe"),
SV("SV", "El Salvador"),
SY("SY", "Syria"),
TC("TC", "Turks & Caicos Is"),
TD("TD", "Chad"),
TH("TH", "Thailand"),
TJ("TJ", "Tajikistan"),
TK("TK", "Tokelau"),
TL("TL", "East Timor"),
TM("TM", "Turkmenistan"),
TN("TN", "Tunisia"),
TO("TO", "Tonga"),
TR("TR", "Turkey"),
TW("TW", "Taiwan"),
UA("UA", "Ukraine"),
US("US", "United States"),
UY("UY", "Uruguay"),
UZ("UZ", "Uzbekistan"),
VE("VE", "Venezuela"),
VN("VN", "Vietnam"),
VU("VU", "Vanuatu"),
WS("WS", "Samoa (western)"),
ZA("ZA", "South Africa");
companion object {
private var codeMap:Map<String, CountryCode>? = null
private var countryMap:Map<String, CountryCode>? = null
fun fromCode(code: String): CountryCode?
= (codeMap ?: entries.associateBy{it.code}.also{codeMap = it})[code]
fun fromCountry(fullName: String): CountryCode?
= (countryMap ?: entries.associateBy{ it.fullName.lowercase() }.also{countryMap = it})[fullName.lowercase()]
}
}
package kore.time
class DstRule(
val fromYear: Int, val toYear: Int,
val startMonth: Int, val startDayRule: String, val startTimeSeconds: Int,
val endMonth: Int, val endDayRule: String, val endTimeSeconds: Int,
val saveSeconds: Int
)
package kore.time
expect fun getCurrentZoneInfo():Zone?
expect fun epochMsNow():Long
package kore.time
class Period(
val stdOffsetSeconds: Int, // 표준 오프셋 (초)
val dstSaveSeconds: Int, // 서머타임 시 추가되는 시간 (초). 없으면 0.
val format: String, // 시간대 약어 (예: "EST", "E%sT")
val untilEpochSeconds: Long // 이 기간이 유효한 마지막 시점 (UTC 에포크 초)
)
package kore.time
class TimeFormat(val format:List<Item>) {
fun interface Item {operator fun invoke(zoneTime:ZoneTime):String}
class Str(val value: String):Item {override fun invoke(zoneTime:ZoneTime):String = value}
companion object{
val Year4:Item = Item{it.year.toString().padStart(4, '0')}
val Year2:Item = Item{(it.year % 100).toString().padStart(2, '0')}
val Month2:Item = Item{it.month.toString().padStart(2, '0')}
val Month1:Item = Item{it.month.toString()}
val Day2:Item = Item{it.day.toString().padStart(2, '0')}
val Day1:Item = Item{it.day.toString()}
val Hour24_2:Item = Item{it.hour.toString().padStart(2, '0')}
val Hour24_1:Item = Item{it.hour.toString()}
val Hour12_2:Item = Item{
val h = if (it.hour == 0 || it.hour == 12) 12 else it.hour % 12
h.toString().padStart(2, '0')
}
val Hour12_1:Item = Item{
val h = if (it.hour == 0 || it.hour == 12) 12 else it.hour % 12
h.toString()
}
val Minute2:Item = Item{it.minute.toString().padStart(2, '0')}
val Minute1:Item = Item{it.minute.toString()}
val Second2:Item = Item{it.second.toString().padStart(2, '0')}
val Second1:Item = Item{it.second.toString()}
val Millisecond3:Item = Item{it.ms.toString().padStart(3, '0')}
val AmPmUpper:Item = Item{if(it.hour < 12) "AM" else "PM"}
val AmPmLower:Item = Item{if(it.hour < 12) "am" else "pm"}
private val TOKEN_MAP = mapOf(
"YYYY" to Year4, "YY" to Year2,
"MM" to Month2, "M" to Month1,
"DD" to Day2, "D" to Day1,
"HH" to Hour24_2, "H" to Hour24_1, "hh" to Hour12_2, "h" to Hour12_1,
"mm" to Minute2, "m" to Minute1, "ss" to Second2, "s" to Second1,
"SSS" to Millisecond3,
"A" to AmPmUpper, "a" to AmPmLower
)
private val SORTED_TOKENS = TOKEN_MAP.keys.sortedByDescending { it.length }
private val cache = mutableMapOf<String, TimeFormat>()
operator fun invoke(format:String):TimeFormat = cache.getOrPut(format) {
val items = mutableListOf<Item>()
var i = 0
val literalBuffer = StringBuilder()
while (i < format.length) {
val matchedToken = SORTED_TOKENS.find { format.startsWith(it, i) }
if (matchedToken != null) {
if (literalBuffer.isNotEmpty()) {
items.add(Str(literalBuffer.toString()))
literalBuffer.clear()
}
items.add(TOKEN_MAP.getValue(matchedToken))
i += matchedToken.length
} else {
literalBuffer.append(format[i])
i++
}
}
if (literalBuffer.isNotEmpty()) items.add(Str(literalBuffer.toString()))
TimeFormat(items)
}
operator fun invoke(vararg items:Item):TimeFormat = TimeFormat(items.toList())
}
fun toString(zoneTime:ZoneTime):String = format.joinToString(""){it(zoneTime)}
}
package kore.time
object TimeUtil {
internal fun isLeapYear(year:Int):Boolean = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
internal fun daysInYear(year:Int):Int = if(isLeapYear(year)) 366 else 365
internal fun daysInMonth(year:Int, month:Int): Int = when(month){
2 -> if (isLeapYear(year)) 29 else 28
4, 6, 9, 11 -> 30
else -> 31
}
internal fun floorDiv(x: Long, y: Long): Long {
var r = x / y
if ((x xor y) < 0 && r * y != x) r -= 1
return r
}
internal fun floorMod(x: Long, y: Long): Long {
val r = x - floorDiv(x, y) * y
return r
}
@PublishedApi internal fun epochDayFromCivil(y0: Int, m: Int, d0: Int): Long {
var y = y0
val d = d0
y -= if (m <= 2) 1 else 0
val era = if (y >= 0) y / 400 else (y - 399) / 400
val yoe = y - era * 400 // [0,399]
val mp = m + if (m > 2) -3 else 9 // Mar=0..Feb=11
val doy = (153 * mp + 2) / 5 + d - 1 // [0,365]
val doe = yoe * 365 + yoe / 4 - yoe / 100 + doy
return era * 146_097L + doe - 719_468L
}
}
@file:Suppress("NOTHING_TO_INLINE")
package kore.time
import kotlin.math.abs
class UtcTime @PublishedApi internal constructor(val epochMs:Long, val offsetMs:Int){
companion object{
@PublishedApi internal val RFC3339_REGEX = """^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(Z|[+-]\d{2}:\d{2})$""".toRegex()
inline operator fun invoke(epochMs:Long, offsetMs:Int = 0):UtcTime = UtcTime(epochMs, offsetMs)
inline operator fun invoke(zoneTime: ZoneTime):UtcTime = zoneTime.toUtcTime()
inline operator fun invoke(saved:String):UtcTime?{
val s = saved.trim()
return if (s.startsWith("zt0:")) { //zt0:info.area:epochMs:offsetMs
val (_, _, epoch, offset) = s.split(":")
UtcTime(epoch.toLong(), offset.toInt())
}else{
val m = RFC3339_REGEX.matchEntire(s) ?: return null
val (ys, ms, ds, hs, mins, ss, fracs, off) = m.destructured
val year = ys.toInt()
val month = ms.toInt()
val day = ds.toInt()
val hour = hs.toInt()
val minute = mins.toInt()
val second = ss.toInt()
val offsetMs = when (off) {
"Z" -> 0
else -> {
val sign = if (off[0] == '-') -1 else 1
val oh = off.substring(1, 3).toInt()
val om = off.substring(4, 6).toInt()
sign * (oh * 3_600_000 + om * 60_000)
}
}
val milli = if (fracs.isEmpty()) 0 else (fracs.padEnd(3, '0').take(3).toInt())
val epochDay = TimeUtil.epochDayFromCivil(year, month, day)
val wallMs = epochDay * 86_400_000L +
hour * 3_600_000L +
minute * 60_000L +
second * 1_000L +
milli
val utcMs = wallMs - offsetMs
UtcTime(utcMs, offsetMs)
}
}
}
val localEpochMs:Long = epochMs + offsetMs
private var isoString:String? = null
fun add(day:Int = 0, hour:Int = 0, minute:Int = 0, second:Int = 0, ms:Int = 0):UtcTime
= UtcTime(
epochMs + day.toLong() * 86_400_000L + hour.toLong() * 3_600_000L + minute.toLong() * 60_000L + second.toLong() * 1_000L + ms.toLong(),
offsetMs
)
inline fun diffMS(other:UtcTime):Long = epochMs - other.epochMs
inline fun diffSecond(other:UtcTime):Long = diffMS(other) / 1000
inline fun diffMinute(other:UtcTime):Long = diffMS(other) / 60000
inline fun diffHour(other:UtcTime):Long = diffMS(other) / 3600000
inline fun diffDay(other:UtcTime):Long = diffMS(other) / 86400000
inline fun year(zone: Zone): Int = toZoneTime(zone).year
inline fun month(zone: Zone): Int = toZoneTime(zone).month
inline fun day(zone: Zone): Int = toZoneTime(zone).day
inline fun hour(zone: Zone): Int = toZoneTime(zone).hour
inline fun minute(zone: Zone): Int = toZoneTime(zone).minute
inline fun second(zone: Zone): Int = toZoneTime(zone).second
inline fun ms(): Int = (((epochMs % 1000L) + 1000L) % 1000L).toInt()
fun toZoneTime(zone: Zone): ZoneTime {
val idx = zone.periodIndexByUtc(epochMs)
val offsetMs = zone.totalOffsetMs[idx]
val localEpochMs = epochMs + offsetMs
val millisPerDay = 86_400_000L
val epochDay = TimeUtil.floorDiv(localEpochMs, millisPerDay)
val millisOfDay = TimeUtil.floorMod(localEpochMs, millisPerDay).toInt()
var year = 1970
var days = epochDay
while (days < 0 || days >= TimeUtil.daysInYear(year)) {
if (days < 0) { year--; days += TimeUtil.daysInYear(year) }
else { days -= TimeUtil.daysInYear(year); year++ }
}
var month = 1
while (days >= TimeUtil.daysInMonth(year, month)) {
days -= TimeUtil.daysInMonth(year, month)
month++
}
val day = (days + 1).toInt()
val hour = millisOfDay / 3_600_000
val minute = (millisOfDay % 3_600_000) / 60_000
val second = (millisOfDay % 60_000) / 1_000
val ms = millisOfDay % 1_000
return ZoneTime(year, month, day, hour, minute, second, ms, zone)
}
override fun equals(other: Any?): Boolean = other is UtcTime && epochMs == other.epochMs
override fun hashCode(): Int = epochMs.hashCode()
private inline fun pad2(n: Int) = n.toString().padStart(2, '0')
private inline fun pad3(n: Int) = n.toString().padStart(3, '0')
private inline fun pad4(n: Int) = n.toString().padStart(4, '0')
fun toISO8601(isRFC3339:Boolean = true):String{
val s = isoString ?: toZoneTime(Zone.ephemeral(offsetMs)).let{zt->
StringBuilder(32)
.append(pad4(zt.year)).append('-')
.append(pad2(zt.month)).append('-')
.append(pad2(zt.day)).append('T')
.append(pad2(zt.hour)).append(':')
.append(pad2(zt.minute)).append(':')
.append(pad2(zt.second)).also{
if(!isRFC3339 || zt.ms != 0) it.append('.').append(pad3(zt.ms))
if (offsetMs == 0) it.append('Z')
else {
val absMs = abs(offsetMs)
it.append(if (offsetMs < 0) '-' else '+')
.append(pad2(absMs / 3_600_000)).append(':')
.append(pad2((absMs % 3_600_000) / 60_000))
}
}.toString().also{isoString = it}
}
return if (!isRFC3339 && s.endsWith("Z")) s.dropLast(1) + "+00:00" else s
}
override fun toString():String = toISO8601()
}
@file:Suppress("NOTHING_TO_INLINE")
package kore.time
import kotlin.math.*
class Zone(val area:String, val countryCode:CountryCode, val lat:Double, val lon:Double, val utcOffset:Int, private val p:List<Period>){
companion object{
private const val EOT_SEC: Long = 253_402_300_799L
private const val EPHEMERAL = "EPHEMERAL"
internal val UTC = Zone(EPHEMERAL, CountryCode.KR, 0.0, 0.0, 0, listOf(Period(0, 0, "UTC", EOT_SEC)))
operator fun get(area:String):Zone? = zones[area.lowercase()]
internal fun ephemeral(offsetMs: Int): Zone = Zone(
EPHEMERAL, CountryCode.KR, 0.0, 0.0, offsetMs / 1000,
listOf(Period(offsetMs / 1000, 0, "UNK", EOT_SEC)) // 초 단위
)
private inline fun Double.rad(): Double = this * PI / 180.0
fun findClose(lat:Double, lon:Double): Zone?{
val candidates = zones.values
if (candidates.isEmpty()) return null
val latRad = lat.rad()
val cosLat = cos(latRad)
var best: Zone? = null
var bestD2 = Double.POSITIVE_INFINITY
for (z in candidates) {
val dLat = (z.lat - lat).rad()
var dLon = (z.lon - lon).rad()
// 날짜변경선 래핑: [-π, π]로 접기
if (dLon > PI) dLon -= 2 * PI
if (dLon < -PI) dLon += 2 * PI
// equirectangular 근사: x=Δλ·cos φ, y=Δφ
val x = dLon * cosLat
val d2 = x * x + dLat * dLat
if (d2 < bestD2) {
bestD2 = d2
best = z
}
}
return best
}
}
private var _periods:List<Period>? = null
val periods get() = _periods ?: (
if(p.isEmpty()) listOf(Period(utcOffset, 0, "FIX", EOT_SEC))
else p.sortedBy{it.untilEpochSeconds}
).also{_periods = it}
private var _totalOffsetMs:IntArray? = null
internal val totalOffsetMs:IntArray get() = _totalOffsetMs?: IntArray(periods.size) { i ->
(periods[i].stdOffsetSeconds + periods[i].dstSaveSeconds) * 1000
}.also{_totalOffsetMs = it}
private var _endExclMs:LongArray? = null
internal val endExclMs:LongArray get() = _endExclMs ?: LongArray(periods.size){i ->
val s = periods[i].untilEpochSeconds
val ms = s * 1000L
if (ms > Long.MAX_VALUE - 1000L) Long.MAX_VALUE else ms + 1000L
}.also{_endExclMs = it}
fun periodIndexByUtc(epochMs: Long): Int {/** epochMs가 속한 period 인덱스 (UTC 기준, 이진 탐색) */
var lo = 0
var hi = endExclMs.size
while (lo < hi) {
val mid = (lo + hi) ushr 1
if (epochMs < endExclMs[mid]) hi = mid else lo = mid + 1
}
return (lo - 1).coerceIn(0, periods.lastIndex)
}
init{
if(area != EPHEMERAL) {
if(area !in zones) zones[area] = this else throw Throwable("Zone already exists: $area")
}
}
}
package kore.time
@PublishedApi internal val zones:HashMap<String, Zone> = hashMapOf(
"africa/abidjan" to AFRICA_ABIDJAN,
"africa/accra" to AFRICA_ACCRA,
"africa/addis_ababa" to AFRICA_ADDIS_ABABA,
"africa/algiers" to AFRICA_ALGIERS,
"africa/asmara" to AFRICA_ASMARA,
"africa/asmera" to AFRICA_ASMERA,
"africa/bamako" to AFRICA_BAMAKO,
"africa/bangui" to AFRICA_BANGUI,
"africa/banjul" to AFRICA_BANJUL,
"africa/bissau" to AFRICA_BISSAU,
"africa/blantyre" to AFRICA_BLANTYRE,
"africa/brazzaville" to AFRICA_BRAZZAVILLE,
"africa/bujumbura" to AFRICA_BUJUMBURA,
"africa/cairo" to AFRICA_CAIRO,
"africa/casablanca" to AFRICA_CASABLANCA,
"africa/ceuta" to AFRICA_CEUTA,
"africa/conakry" to AFRICA_CONAKRY,
"africa/dakar" to AFRICA_DAKAR,
"africa/dar_es_salaam" to AFRICA_DAR_ES_SALAAM,
"africa/djibouti" to AFRICA_DJIBOUTI,
"africa/douala" to AFRICA_DOUALA,
"africa/el_aaiun" to AFRICA_EL_AAIUN,
"africa/freetown" to AFRICA_FREETOWN,
"africa/gaborone" to AFRICA_GABORONE,
"africa/harare" to AFRICA_HARARE,
"africa/johannesburg" to AFRICA_JOHANNESBURG,
"africa/juba" to AFRICA_JUBA,
"africa/kampala" to AFRICA_KAMPALA,
"africa/khartoum" to AFRICA_KHARTOUM,
"africa/kigali" to AFRICA_KIGALI,
"africa/kinshasa" to AFRICA_KINSHASA,
"africa/lagos" to AFRICA_LAGOS,
"africa/libreville" to AFRICA_LIBREVILLE,
"africa/lome" to AFRICA_LOME,
"africa/luanda" to AFRICA_LUANDA,
"africa/lubumbashi" to AFRICA_LUBUMBASHI,
"africa/lusaka" to AFRICA_LUSAKA,
"africa/malabo" to AFRICA_MALABO,
"africa/maputo" to AFRICA_MAPUTO,
"africa/maseru" to AFRICA_MASERU,
"africa/mbabane" to AFRICA_MBABANE,
"africa/mogadishu" to AFRICA_MOGADISHU,
"africa/monrovia" to AFRICA_MONROVIA,
"africa/nairobi" to AFRICA_NAIROBI,
"africa/ndjamena" to AFRICA_NDJAMENA,
"africa/niamey" to AFRICA_NIAMEY,
"africa/nouakchott" to AFRICA_NOUAKCHOTT,
"africa/ouagadougou" to AFRICA_OUAGADOUGOU,
"africa/porto-novo" to AFRICA_PORTO_NOVO,
"africa/sao_tome" to AFRICA_SAO_TOME,
"africa/timbuktu" to AFRICA_TIMBUKTU,
"africa/tripoli" to AFRICA_TRIPOLI,
"africa/tunis" to AFRICA_TUNIS,
"africa/windhoek" to AFRICA_WINDHOEK,
"america/adak" to AMERICA_ADAK,
"america/anchorage" to AMERICA_ANCHORAGE,
"america/anguilla" to AMERICA_ANGUILLA,
"america/antigua" to AMERICA_ANTIGUA,
"america/araguaina" to AMERICA_ARAGUAINA,
"america/argentina/buenos_aires" to AMERICA_ARGENTINA_BUENOS_AIRES,
"america/argentina/catamarca" to AMERICA_ARGENTINA_CATAMARCA,
"america/argentina/comodrivadavia" to AMERICA_ARGENTINA_COMODRIVADAVIA,
"america/argentina/cordoba" to AMERICA_ARGENTINA_CORDOBA,
"america/argentina/jujuy" to AMERICA_ARGENTINA_JUJUY,
"america/argentina/la_rioja" to AMERICA_ARGENTINA_LA_RIOJA,
"america/argentina/mendoza" to AMERICA_ARGENTINA_MENDOZA,
"america/argentina/rio_gallegos" to AMERICA_ARGENTINA_RIO_GALLEGOS,
"america/argentina/salta" to AMERICA_ARGENTINA_SALTA,
"america/argentina/san_juan" to AMERICA_ARGENTINA_SAN_JUAN,
"america/argentina/san_luis" to AMERICA_ARGENTINA_SAN_LUIS,
"america/argentina/tucuman" to AMERICA_ARGENTINA_TUCUMAN,
"america/argentina/ushuaia" to AMERICA_ARGENTINA_USHUAIA,
"america/aruba" to AMERICA_ARUBA,
"america/asuncion" to AMERICA_ASUNCION,
"america/atikokan" to AMERICA_ATIKOKAN,
"america/atka" to AMERICA_ATKA,
"america/bahia" to AMERICA_BAHIA,
"america/bahia_banderas" to AMERICA_BAHIA_BANDERAS,
"america/barbados" to AMERICA_BARBADOS,
"america/belem" to AMERICA_BELEM,
"america/belize" to AMERICA_BELIZE,
"america/blanc-sablon" to AMERICA_BLANC_SABLON,
"america/boa_vista" to AMERICA_BOA_VISTA,
"america/bogota" to AMERICA_BOGOTA,
"america/boise" to AMERICA_BOISE,
"america/buenos_aires" to AMERICA_BUENOS_AIRES,
"america/cambridge_bay" to AMERICA_CAMBRIDGE_BAY,
"america/campo_grande" to AMERICA_CAMPO_GRANDE,
"america/cancun" to AMERICA_CANCUN,
"america/caracas" to AMERICA_CARACAS,
"america/catamarca" to AMERICA_CATAMARCA,
"america/cayenne" to AMERICA_CAYENNE,
"america/cayman" to AMERICA_CAYMAN,
"america/chicago" to AMERICA_CHICAGO,
"america/chihuahua" to AMERICA_CHIHUAHUA,
"america/ciudad_juarez" to AMERICA_CIUDAD_JUAREZ,
"america/coral_harbour" to AMERICA_CORAL_HARBOUR,
"america/cordoba" to AMERICA_CORDOBA,
"america/costa_rica" to AMERICA_COSTA_RICA,
"america/creston" to AMERICA_CRESTON,
"america/cuiaba" to AMERICA_CUIABA,
"america/curacao" to AMERICA_CURACAO,
"america/danmarkshavn" to AMERICA_DANMARKSHAVN,
"america/dawson" to AMERICA_DAWSON,
"america/dawson_creek" to AMERICA_DAWSON_CREEK,
"america/denver" to AMERICA_DENVER,
"america/detroit" to AMERICA_DETROIT,
"america/dominica" to AMERICA_DOMINICA,
"america/edmonton" to AMERICA_EDMONTON,
"america/eirunepe" to AMERICA_EIRUNEPE,
"america/el_salvador" to AMERICA_EL_SALVADOR,
"america/ensenada" to AMERICA_ENSENADA,
"america/fort_nelson" to AMERICA_FORT_NELSON,
"america/fort_wayne" to AMERICA_FORT_WAYNE,
"america/fortaleza" to AMERICA_FORTALEZA,
"america/glace_bay" to AMERICA_GLACE_BAY,
"america/godthab" to AMERICA_GODTHAB,
"america/goose_bay" to AMERICA_GOOSE_BAY,
"america/grand_turk" to AMERICA_GRAND_TURK,
"america/grenada" to AMERICA_GRENADA,
"america/guadeloupe" to AMERICA_GUADELOUPE,
"america/guatemala" to AMERICA_GUATEMALA,
"america/guayaquil" to AMERICA_GUAYAQUIL,
"america/guyana" to AMERICA_GUYANA,
"america/halifax" to AMERICA_HALIFAX,
"america/havana" to AMERICA_HAVANA,
"america/hermosillo" to AMERICA_HERMOSILLO,
"america/indiana/indianapolis" to AMERICA_INDIANA_INDIANAPOLIS,
"america/indiana/knox" to AMERICA_INDIANA_KNOX,
"america/indiana/marengo" to AMERICA_INDIANA_MARENGO,
"america/indiana/petersburg" to AMERICA_INDIANA_PETERSBURG,
"america/indiana/tell_city" to AMERICA_INDIANA_TELL_CITY,
"america/indiana/vevay" to AMERICA_INDIANA_VEVAY,
"america/indiana/vincennes" to AMERICA_INDIANA_VINCENNES,
"america/indiana/winamac" to AMERICA_INDIANA_WINAMAC,
"america/indianapolis" to AMERICA_INDIANAPOLIS,
"america/inuvik" to AMERICA_INUVIK,
"america/iqaluit" to AMERICA_IQALUIT,
"america/jamaica" to AMERICA_JAMAICA,
"america/jujuy" to AMERICA_JUJUY,
"america/juneau" to AMERICA_JUNEAU,
"america/kentucky/louisville" to AMERICA_KENTUCKY_LOUISVILLE,
"america/kentucky/monticello" to AMERICA_KENTUCKY_MONTICELLO,
"america/knox_in" to AMERICA_KNOX_IN,
"america/kralendijk" to AMERICA_KRALENDIJK,
"america/la_paz" to AMERICA_LA_PAZ,
"america/lima" to AMERICA_LIMA,
"america/los_angeles" to AMERICA_LOS_ANGELES,
"america/louisville" to AMERICA_LOUISVILLE,
"america/lower_princes" to AMERICA_LOWER_PRINCES,
"america/maceio" to AMERICA_MACEIO,
"america/managua" to AMERICA_MANAGUA,
"america/manaus" to AMERICA_MANAUS,
"america/marigot" to AMERICA_MARIGOT,
"america/martinique" to AMERICA_MARTINIQUE,
"america/matamoros" to AMERICA_MATAMOROS,
"america/mazatlan" to AMERICA_MAZATLAN,
"america/mendoza" to AMERICA_MENDOZA,
"america/menominee" to AMERICA_MENOMINEE,
"america/merida" to AMERICA_MERIDA,
"america/metlakatla" to AMERICA_METLAKATLA,
"america/mexico_city" to AMERICA_MEXICO_CITY,
"america/miquelon" to AMERICA_MIQUELON,
"america/moncton" to AMERICA_MONCTON,
"america/monterrey" to AMERICA_MONTERREY,
"america/montevideo" to AMERICA_MONTEVIDEO,
"america/montreal" to AMERICA_MONTREAL,
"america/montserrat" to AMERICA_MONTSERRAT,
"america/nassau" to AMERICA_NASSAU,
"america/new_york" to AMERICA_NEW_YORK,
"america/nipigon" to AMERICA_NIPIGON,
"america/nome" to AMERICA_NOME,
"america/noronha" to AMERICA_NORONHA,
"america/north_dakota/beulah" to AMERICA_NORTH_DAKOTA_BEULAH,
"america/north_dakota/center" to AMERICA_NORTH_DAKOTA_CENTER,
"america/north_dakota/new_salem" to AMERICA_NORTH_DAKOTA_NEW_SALEM,
"america/nuuk" to AMERICA_NUUK,
"america/ojinaga" to AMERICA_OJINAGA,
"america/panama" to AMERICA_PANAMA,
"america/pangnirtung" to AMERICA_PANGNIRTUNG,
"america/paramaribo" to AMERICA_PARAMARIBO,
"america/phoenix" to AMERICA_PHOENIX,
"america/port-au-prince" to AMERICA_PORT_AU_PRINCE,
"america/port_of_spain" to AMERICA_PORT_OF_SPAIN,
"america/porto_acre" to AMERICA_PORTO_ACRE,
"america/porto_velho" to AMERICA_PORTO_VELHO,
"america/puerto_rico" to AMERICA_PUERTO_RICO,
"america/punta_arenas" to AMERICA_PUNTA_ARENAS,
"america/rainy_river" to AMERICA_RAINY_RIVER,
"america/rankin_inlet" to AMERICA_RANKIN_INLET,
"america/recife" to AMERICA_RECIFE,
"america/regina" to AMERICA_REGINA,
"america/resolute" to AMERICA_RESOLUTE,
"america/rio_branco" to AMERICA_RIO_BRANCO,
"america/rosario" to AMERICA_ROSARIO,
"america/santa_isabel" to AMERICA_SANTA_ISABEL,
"america/santarem" to AMERICA_SANTAREM,
"america/santiago" to AMERICA_SANTIAGO,
"america/santo_domingo" to AMERICA_SANTO_DOMINGO,
"america/sao_paulo" to AMERICA_SAO_PAULO,
"america/scoresbysund" to AMERICA_SCORESBYSUND,
"america/shiprock" to AMERICA_SHIPROCK,
"america/sitka" to AMERICA_SITKA,
"america/st_barthelemy" to AMERICA_ST_BARTHELEMY,
"america/st_johns" to AMERICA_ST_JOHNS,
"america/st_kitts" to AMERICA_ST_KITTS,
"america/st_lucia" to AMERICA_ST_LUCIA,
"america/st_thomas" to AMERICA_ST_THOMAS,
"america/st_vincent" to AMERICA_ST_VINCENT,
"america/swift_current" to AMERICA_SWIFT_CURRENT,
"america/tegucigalpa" to AMERICA_TEGUCIGALPA,
"america/thule" to AMERICA_THULE,
"america/thunder_bay" to AMERICA_THUNDER_BAY,
"america/tijuana" to AMERICA_TIJUANA,
"america/toronto" to AMERICA_TORONTO,
"america/tortola" to AMERICA_TORTOLA,
"america/vancouver" to AMERICA_VANCOUVER,
"america/virgin" to AMERICA_VIRGIN,
"america/whitehorse" to AMERICA_WHITEHORSE,
"america/winnipeg" to AMERICA_WINNIPEG,
"america/yakutat" to AMERICA_YAKUTAT,
"america/yellowknife" to AMERICA_YELLOWKNIFE,
"antarctica/casey" to ANTARCTICA_CASEY,
"antarctica/davis" to ANTARCTICA_DAVIS,
"antarctica/dumontdurville" to ANTARCTICA_DUMONTDURVILLE,
"antarctica/macquarie" to ANTARCTICA_MACQUARIE,
"antarctica/mawson" to ANTARCTICA_MAWSON,
"antarctica/mcmurdo" to ANTARCTICA_MCMURDO,
"antarctica/palmer" to ANTARCTICA_PALMER,
"antarctica/rothera" to ANTARCTICA_ROTHERA,
"antarctica/south_pole" to ANTARCTICA_SOUTH_POLE,
"antarctica/syowa" to ANTARCTICA_SYOWA,
"antarctica/troll" to ANTARCTICA_TROLL,
"antarctica/vostok" to ANTARCTICA_VOSTOK,
"arctic/longyearbyen" to ARCTIC_LONGYEARBYEN,
"asia/aden" to ASIA_ADEN,
"asia/almaty" to ASIA_ALMATY,
"asia/amman" to ASIA_AMMAN,
"asia/anadyr" to ASIA_ANADYR,
"asia/aqtau" to ASIA_AQTAU,
"asia/aqtobe" to ASIA_AQTOBE,
"asia/ashgabat" to ASIA_ASHGABAT,
"asia/ashkhabad" to ASIA_ASHKHABAD,
"asia/atyrau" to ASIA_ATYRAU,
"asia/baghdad" to ASIA_BAGHDAD,
"asia/bahrain" to ASIA_BAHRAIN,
"asia/baku" to ASIA_BAKU,
"asia/bangkok" to ASIA_BANGKOK,
"asia/barnaul" to ASIA_BARNAUL,
"asia/beirut" to ASIA_BEIRUT,
"asia/bishkek" to ASIA_BISHKEK,
"asia/brunei" to ASIA_BRUNEI,
"asia/calcutta" to ASIA_CALCUTTA,
"asia/chita" to ASIA_CHITA,
"asia/choibalsan" to ASIA_CHOIBALSAN,
"asia/chongqing" to ASIA_CHONGQING,
"asia/chungking" to ASIA_CHUNGKING,
"asia/colombo" to ASIA_COLOMBO,
"asia/dacca" to ASIA_DACCA,
"asia/damascus" to ASIA_DAMASCUS,
"asia/dhaka" to ASIA_DHAKA,
"asia/dili" to ASIA_DILI,
"asia/dubai" to ASIA_DUBAI,
"asia/dushanbe" to ASIA_DUSHANBE,
"asia/famagusta" to ASIA_FAMAGUSTA,
"asia/gaza" to ASIA_GAZA,
"asia/harbin" to ASIA_HARBIN,
"asia/hebron" to ASIA_HEBRON,
"asia/ho_chi_minh" to ASIA_HO_CHI_MINH,
"asia/hong_kong" to ASIA_HONG_KONG,
"asia/hovd" to ASIA_HOVD,
"asia/irkutsk" to ASIA_IRKUTSK,
"asia/istanbul" to ASIA_ISTANBUL,
"asia/jakarta" to ASIA_JAKARTA,
"asia/jayapura" to ASIA_JAYAPURA,
"asia/jerusalem" to ASIA_JERUSALEM,
"asia/kabul" to ASIA_KABUL,
"asia/kamchatka" to ASIA_KAMCHATKA,
"asia/karachi" to ASIA_KARACHI,
"asia/kashgar" to ASIA_KASHGAR,
"asia/kathmandu" to ASIA_KATHMANDU,
"asia/katmandu" to ASIA_KATMANDU,
"asia/khandyga" to ASIA_KHANDYGA,
"asia/kolkata" to ASIA_KOLKATA,
"asia/krasnoyarsk" to ASIA_KRASNOYARSK,
"asia/kuala_lumpur" to ASIA_KUALA_LUMPUR,
"asia/kuching" to ASIA_KUCHING,
"asia/kuwait" to ASIA_KUWAIT,
"asia/macao" to ASIA_MACAO,
"asia/macau" to ASIA_MACAU,
"asia/magadan" to ASIA_MAGADAN,
"asia/makassar" to ASIA_MAKASSAR,
"asia/manila" to ASIA_MANILA,
"asia/muscat" to ASIA_MUSCAT,
"asia/nicosia" to ASIA_NICOSIA,
"asia/novokuznetsk" to ASIA_NOVOKUZNETSK,
"asia/novosibirsk" to ASIA_NOVOSIBIRSK,
"asia/omsk" to ASIA_OMSK,
"asia/oral" to ASIA_ORAL,
"asia/phnom_penh" to ASIA_PHNOM_PENH,
"asia/pontianak" to ASIA_PONTIANAK,
"asia/pyongyang" to ASIA_PYONGYANG,
"asia/qatar" to ASIA_QATAR,
"asia/qostanay" to ASIA_QOSTANAY,
"asia/qyzylorda" to ASIA_QYZYLORDA,
"asia/rangoon" to ASIA_RANGOON,
"asia/riyadh" to ASIA_RIYADH,
"asia/saigon" to ASIA_SAIGON,
"asia/sakhalin" to ASIA_SAKHALIN,
"asia/samarkand" to ASIA_SAMARKAND,
"asia/seoul" to ASIA_SEOUL,
"asia/shanghai" to ASIA_SHANGHAI,
"asia/singapore" to ASIA_SINGAPORE,
"asia/srednekolymsk" to ASIA_SREDNEKOLYMSK,
"asia/taipei" to ASIA_TAIPEI,
"asia/tashkent" to ASIA_TASHKENT,
"asia/tbilisi" to ASIA_TBILISI,
"asia/tehran" to ASIA_TEHRAN,
"asia/tel_aviv" to ASIA_TEL_AVIV,
"asia/thimbu" to ASIA_THIMBU,
"asia/thimphu" to ASIA_THIMPHU,
"asia/tokyo" to ASIA_TOKYO,
"asia/tomsk" to ASIA_TOMSK,
"asia/ujung_pandang" to ASIA_UJUNG_PANDANG,
"asia/ulaanbaatar" to ASIA_ULAANBAATAR,
"asia/ulan_bator" to ASIA_ULAN_BATOR,
"asia/urumqi" to ASIA_URUMQI,
"asia/ust-nera" to ASIA_UST_NERA,
"asia/vientiane" to ASIA_VIENTIANE,
"asia/vladivostok" to ASIA_VLADIVOSTOK,
"asia/yakutsk" to ASIA_YAKUTSK,
"asia/yangon" to ASIA_YANGON,
"asia/yekaterinburg" to ASIA_YEKATERINBURG,
"asia/yerevan" to ASIA_YEREVAN,
"atlantic/azores" to ATLANTIC_AZORES,
"atlantic/bermuda" to ATLANTIC_BERMUDA,
"atlantic/canary" to ATLANTIC_CANARY,
"atlantic/cape_verde" to ATLANTIC_CAPE_VERDE,
"atlantic/faeroe" to ATLANTIC_FAEROE,
"atlantic/faroe" to ATLANTIC_FAROE,
"atlantic/jan_mayen" to ATLANTIC_JAN_MAYEN,
"atlantic/madeira" to ATLANTIC_MADEIRA,
"atlantic/reykjavik" to ATLANTIC_REYKJAVIK,
"atlantic/south_georgia" to ATLANTIC_SOUTH_GEORGIA,
"atlantic/st_helena" to ATLANTIC_ST_HELENA,
"atlantic/stanley" to ATLANTIC_STANLEY,
"australia/act" to AUSTRALIA_ACT,
"australia/adelaide" to AUSTRALIA_ADELAIDE,
"australia/brisbane" to AUSTRALIA_BRISBANE,
"australia/broken_hill" to AUSTRALIA_BROKEN_HILL,
"australia/canberra" to AUSTRALIA_CANBERRA,
"australia/currie" to AUSTRALIA_CURRIE,
"australia/darwin" to AUSTRALIA_DARWIN,
"australia/eucla" to AUSTRALIA_EUCLA,
"australia/hobart" to AUSTRALIA_HOBART,
"australia/lhi" to AUSTRALIA_LHI,
"australia/lindeman" to AUSTRALIA_LINDEMAN,
"australia/lord_howe" to AUSTRALIA_LORD_HOWE,
"australia/melbourne" to AUSTRALIA_MELBOURNE,
"australia/nsw" to AUSTRALIA_NSW,
"australia/north" to AUSTRALIA_NORTH,
"australia/perth" to AUSTRALIA_PERTH,
"australia/queensland" to AUSTRALIA_QUEENSLAND,
"australia/south" to AUSTRALIA_SOUTH,
"australia/sydney" to AUSTRALIA_SYDNEY,
"australia/tasmania" to AUSTRALIA_TASMANIA,
"australia/victoria" to AUSTRALIA_VICTORIA,
"australia/west" to AUSTRALIA_WEST,
"australia/yancowinna" to AUSTRALIA_YANCOWINNA,
"brazil/acre" to BRAZIL_ACRE,
"brazil/denoronha" to BRAZIL_DENORONHA,
"brazil/east" to BRAZIL_EAST,
"brazil/west" to BRAZIL_WEST,
"cet" to CET,
"cst6cdt" to CST6CDT,
"canada/atlantic" to CANADA_ATLANTIC,
"canada/central" to CANADA_CENTRAL,
"canada/eastern" to CANADA_EASTERN,
"canada/mountain" to CANADA_MOUNTAIN,
"canada/newfoundland" to CANADA_NEWFOUNDLAND,
"canada/pacific" to CANADA_PACIFIC,
"canada/saskatchewan" to CANADA_SASKATCHEWAN,
"canada/yukon" to CANADA_YUKON,
"chile/continental" to CHILE_CONTINENTAL,
"chile/easterisland" to CHILE_EASTERISLAND,
"cuba" to CUBA,
"eet" to EET,
"est" to EST,
"est5edt" to EST5EDT,
"egypt" to EGYPT,
"eire" to EIRE,
"europe/amsterdam" to EUROPE_AMSTERDAM,
"europe/andorra" to EUROPE_ANDORRA,
"europe/astrakhan" to EUROPE_ASTRAKHAN,
"europe/athens" to EUROPE_ATHENS,
"europe/belfast" to EUROPE_BELFAST,
"europe/belgrade" to EUROPE_BELGRADE,
"europe/berlin" to EUROPE_BERLIN,
"europe/bratislava" to EUROPE_BRATISLAVA,
"europe/brussels" to EUROPE_BRUSSELS,
"europe/bucharest" to EUROPE_BUCHAREST,
"europe/budapest" to EUROPE_BUDAPEST,
"europe/busingen" to EUROPE_BUSINGEN,
"europe/chisinau" to EUROPE_CHISINAU,
"europe/copenhagen" to EUROPE_COPENHAGEN,
"europe/dublin" to EUROPE_DUBLIN,
"europe/gibraltar" to EUROPE_GIBRALTAR,
"europe/guernsey" to EUROPE_GUERNSEY,
"europe/helsinki" to EUROPE_HELSINKI,
"europe/isle_of_man" to EUROPE_ISLE_OF_MAN,
"europe/istanbul" to EUROPE_ISTANBUL,
"europe/jersey" to EUROPE_JERSEY,
"europe/kaliningrad" to EUROPE_KALININGRAD,
"europe/kiev" to EUROPE_KIEV,
"europe/kirov" to EUROPE_KIROV,
"europe/kyiv" to EUROPE_KYIV,
"europe/lisbon" to EUROPE_LISBON,
"europe/ljubljana" to EUROPE_LJUBLJANA,
"europe/london" to EUROPE_LONDON,
"europe/luxembourg" to EUROPE_LUXEMBOURG,
"europe/madrid" to EUROPE_MADRID,
"europe/malta" to EUROPE_MALTA,
"europe/mariehamn" to EUROPE_MARIEHAMN,
"europe/minsk" to EUROPE_MINSK,
"europe/monaco" to EUROPE_MONACO,
"europe/moscow" to EUROPE_MOSCOW,
"europe/nicosia" to EUROPE_NICOSIA,
"europe/oslo" to EUROPE_OSLO,
"europe/paris" to EUROPE_PARIS,
"europe/podgorica" to EUROPE_PODGORICA,
"europe/prague" to EUROPE_PRAGUE,
"europe/riga" to EUROPE_RIGA,
"europe/rome" to EUROPE_ROME,
"europe/samara" to EUROPE_SAMARA,
"europe/san_marino" to EUROPE_SAN_MARINO,
"europe/sarajevo" to EUROPE_SARAJEVO,
"europe/saratov" to EUROPE_SARATOV,
"europe/simferopol" to EUROPE_SIMFEROPOL,
"europe/skopje" to EUROPE_SKOPJE,
"europe/sofia" to EUROPE_SOFIA,
"europe/stockholm" to EUROPE_STOCKHOLM,
"europe/tallinn" to EUROPE_TALLINN,
"europe/tirane" to EUROPE_TIRANE,
"europe/tiraspol" to EUROPE_TIRASPOL,
"europe/ulyanovsk" to EUROPE_ULYANOVSK,
"europe/uzhgorod" to EUROPE_UZHGOROD,
"europe/vaduz" to EUROPE_VADUZ,
"europe/vatican" to EUROPE_VATICAN,
"europe/vienna" to EUROPE_VIENNA,
"europe/vilnius" to EUROPE_VILNIUS,
"europe/volgograd" to EUROPE_VOLGOGRAD,
"europe/warsaw" to EUROPE_WARSAW,
"europe/zagreb" to EUROPE_ZAGREB,
"europe/zaporozhye" to EUROPE_ZAPOROZHYE,
"europe/zurich" to EUROPE_ZURICH,
"gb" to GB,
"gb-eire" to GB_EIRE,
"hst" to HST,
"hongkong" to HONGKONG,
"iceland" to ICELAND,
"indian/antananarivo" to INDIAN_ANTANANARIVO,
"indian/chagos" to INDIAN_CHAGOS,
"indian/christmas" to INDIAN_CHRISTMAS,
"indian/cocos" to INDIAN_COCOS,
"indian/comoro" to INDIAN_COMORO,
"indian/kerguelen" to INDIAN_KERGUELEN,
"indian/mahe" to INDIAN_MAHE,
"indian/maldives" to INDIAN_MALDIVES,
"indian/mauritius" to INDIAN_MAURITIUS,
"indian/mayotte" to INDIAN_MAYOTTE,
"indian/reunion" to INDIAN_REUNION,
"iran" to IRAN,
"israel" to ISRAEL,
"jamaica" to JAMAICA,
"japan" to JAPAN,
"kwajalein" to KWAJALEIN,
"libya" to LIBYA,
"met" to MET,
"mst" to MST,
"mst7mdt" to MST7MDT,
"mexico/bajanorte" to MEXICO_BAJANORTE,
"mexico/bajasur" to MEXICO_BAJASUR,
"mexico/general" to MEXICO_GENERAL,
"nz" to NZ,
"nz-chat" to NZ_CHAT,
"navajo" to NAVAJO,
"prc" to PRC,
"pst8pdt" to PST8PDT,
"pacific/apia" to PACIFIC_APIA,
"pacific/auckland" to PACIFIC_AUCKLAND,
"pacific/bougainville" to PACIFIC_BOUGAINVILLE,
"pacific/chatham" to PACIFIC_CHATHAM,
"pacific/chuuk" to PACIFIC_CHUUK,
"pacific/easter" to PACIFIC_EASTER,
"pacific/efate" to PACIFIC_EFATE,
"pacific/enderbury" to PACIFIC_ENDERBURY,
"pacific/fakaofo" to PACIFIC_FAKAOFO,
"pacific/fiji" to PACIFIC_FIJI,
"pacific/funafuti" to PACIFIC_FUNAFUTI,
"pacific/galapagos" to PACIFIC_GALAPAGOS,
"pacific/gambier" to PACIFIC_GAMBIER,
"pacific/guadalcanal" to PACIFIC_GUADALCANAL,
"pacific/guam" to PACIFIC_GUAM,
"pacific/honolulu" to PACIFIC_HONOLULU,
"pacific/johnston" to PACIFIC_JOHNSTON,
"pacific/kanton" to PACIFIC_KANTON,
"pacific/kiritimati" to PACIFIC_KIRITIMATI,
"pacific/kosrae" to PACIFIC_KOSRAE,
"pacific/kwajalein" to PACIFIC_KWAJALEIN,
"pacific/majuro" to PACIFIC_MAJURO,
"pacific/marquesas" to PACIFIC_MARQUESAS,
"pacific/midway" to PACIFIC_MIDWAY,
"pacific/nauru" to PACIFIC_NAURU,
"pacific/niue" to PACIFIC_NIUE,
"pacific/norfolk" to PACIFIC_NORFOLK,
"pacific/noumea" to PACIFIC_NOUMEA,
"pacific/pago_pago" to PACIFIC_PAGO_PAGO,
"pacific/palau" to PACIFIC_PALAU,
"pacific/pitcairn" to PACIFIC_PITCAIRN,
"pacific/pohnpei" to PACIFIC_POHNPEI,
"pacific/ponape" to PACIFIC_PONAPE,
"pacific/port_moresby" to PACIFIC_PORT_MORESBY,
"pacific/rarotonga" to PACIFIC_RAROTONGA,
"pacific/saipan" to PACIFIC_SAIPAN,
"pacific/samoa" to PACIFIC_SAMOA,
"pacific/tahiti" to PACIFIC_TAHITI,
"pacific/tarawa" to PACIFIC_TARAWA,
"pacific/tongatapu" to PACIFIC_TONGATAPU,
"pacific/truk" to PACIFIC_TRUK,
"pacific/wake" to PACIFIC_WAKE,
"pacific/wallis" to PACIFIC_WALLIS,
"pacific/yap" to PACIFIC_YAP,
"poland" to POLAND,
"portugal" to PORTUGAL,
"roc" to ROC,
"rok" to ROK,
"singapore" to SINGAPORE,
"turkey" to TURKEY,
"us/alaska" to US_ALASKA,
"us/aleutian" to US_ALEUTIAN,
"us/arizona" to US_ARIZONA,
"us/central" to US_CENTRAL,
"us/east-indiana" to US_EAST_INDIANA,
"us/eastern" to US_EASTERN,
"us/hawaii" to US_HAWAII,
"us/indiana-starke" to US_INDIANA_STARKE,
"us/michigan" to US_MICHIGAN,
"us/mountain" to US_MOUNTAIN,
"us/pacific" to US_PACIFIC,
"us/samoa" to US_SAMOA,
"w-su" to W_SU,
"wet" to WET,
)
package kore.time
class ZoneTime(
val year:Int, val month:Int, val day:Int,
val hour:Int, val minute:Int, val second:Int, val ms:Int, val zone:Zone){
companion object{
operator fun invoke(saved:String):ZoneTime?{
val (_, id, epoch, offset) = saved.split(":")
return Zone[id]?.let{
UtcTime(epoch.toLong(), offset.toInt()).toZoneTime(it)
}
}
}
fun save():String = toUtcTime().let { "zt0:${zone.area}:${it.epochMs}:${it.offsetMs}"}
fun toString(format:TimeFormat):String = format.toString(this)
fun toString(format:String):String = TimeFormat(format).toString(this)
override fun toString():String = toString("YYYY-MM-DDTHH:mm:ss.SSS")
@PublishedApi internal var utcTime:UtcTime? = null
fun toUtcTime():UtcTime {
utcTime?.let { return it }
val epochDay = TimeUtil.epochDayFromCivil(year, month, day)
val wallMs = epochDay * 86_400_000L +
hour * 3_600_000L +
minute * 60_000L +
second * 1_000L +
ms
val periods = zone.periods
if (periods.size == 1 && periods[0].dstSaveSeconds == 0) {
val off = periods[0].stdOffsetSeconds * 1000
return UtcTime(wallMs - off, off).also { utcTime = it }
}
var startUtc = Long.MIN_VALUE
var hasChosen = false
var chosenEpoch = 0L
var chosenOffset = 0
var haveNext = false
var nextLocalStart = 0L
var nextOffset = 0
val n = periods.size
for (i in 0 until n) {
val endUtcExcl = zone.endExclMs[i]
val offMs = zone.totalOffsetMs[i]
val localStart = startUtc + offMs
val localEnd = endUtcExcl + offMs
if (wallMs in localStart..<localEnd) {
val candidateEpoch = wallMs - offMs
if (!hasChosen || offMs > chosenOffset) {
hasChosen = true
chosenEpoch = candidateEpoch
chosenOffset = offMs
}
} else if (wallMs < localStart) {
if (!haveNext || localStart < nextLocalStart) {
haveNext = true
nextLocalStart = localStart
nextOffset = offMs
}
}
startUtc = endUtcExcl
}
val result = when {
hasChosen -> UtcTime(chosenEpoch, chosenOffset)
haveNext -> UtcTime(nextLocalStart - nextOffset, nextOffset) // gap
else -> {
val lastOff = zone.totalOffsetMs[n - 1]
UtcTime(wallMs - lastOff, lastOff) // after last period
}
}
return result.also { utcTime = it }
}
fun toZoneTime(otherInfo:Zone):ZoneTime = if(zone.area == otherInfo.area) this else toUtcTime().toZoneTime(otherInfo)
fun diffYear(other:ZoneTime):Long = diffMonth(other) / 12
fun diffMonth(other:ZoneTime):Long {
val thisUtc = toUtcTime().toZoneTime(Zone.UTC)
val otherUtc = other.toUtcTime().toZoneTime(Zone.UTC)
return (thisUtc.year - otherUtc.year) * 12L + (thisUtc.month - otherUtc.month)
}
fun diffDay(other:ZoneTime):Long = (toUtcTime().diffDay(other.toUtcTime()))
fun diffHour(other:ZoneTime):Long = toUtcTime().diffHour(other.toUtcTime())
fun diffMinute(other:ZoneTime):Long = toUtcTime().diffMinute(other.toUtcTime())
fun diffSecond(other:ZoneTime):Long = toUtcTime().diffSecond(other.toUtcTime())
fun diffMS(other:ZoneTime):Long = toUtcTime().diffMS(other.toUtcTime())
private fun addCalendar(year: Int = 0, month: Int = 0): ZoneTime {
var newMonth = this.month + month
var newYear = this.year + year
newYear += (newMonth - 1) / 12
newMonth = (newMonth - 1) % 12 + 1
if (newMonth <= 0) {
newMonth += 12
newYear--
}
val newDay = this.day.coerceAtMost(TimeUtil.daysInMonth(newYear, newMonth))
return ZoneTime(newYear, newMonth, newDay, hour, minute, second, ms, zone)
}
fun addYear(year: Int): ZoneTime = toUtcTime().toZoneTime(zone).addCalendar(year = year)
fun addMonth(month: Int): ZoneTime = toUtcTime().toZoneTime(zone).addCalendar(month = month)
fun addDay(day: Int): ZoneTime = toUtcTime().add(day = day).toZoneTime(zone)
fun addHour(hour: Int): ZoneTime = toUtcTime().add(hour = hour).toZoneTime(zone)
fun addMinute(minute: Int): ZoneTime = toUtcTime().add(minute = minute).toZoneTime(zone)
fun addSecond(second: Int): ZoneTime = toUtcTime().add(second = second).toZoneTime(zone)
fun addMS(ms: Int): ZoneTime = toUtcTime().add(ms = ms).toZoneTime(zone)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment