ICU-21372 getOffsetFromLocal for C and C++

See #1610
diff --git a/icu4c/source/i18n/basictz.cpp b/icu4c/source/i18n/basictz.cpp
index 12c9e16..f98625a 100644
--- a/icu4c/source/i18n/basictz.cpp
+++ b/icu4c/source/i18n/basictz.cpp
@@ -547,14 +547,23 @@
 }
 
 void
-BasicTimeZone::getOffsetFromLocal(UDate /*date*/, int32_t /*nonExistingTimeOpt*/, int32_t /*duplicatedTimeOpt*/,
-                            int32_t& /*rawOffset*/, int32_t& /*dstOffset*/, UErrorCode& status) const {
+BasicTimeZone::getOffsetFromLocal(UDate /*date*/, UTimeZoneLocalOption /*nonExistingTimeOpt*/,
+                                  UTimeZoneLocalOption /*duplicatedTimeOpt*/,
+                                  int32_t& /*rawOffset*/, int32_t& /*dstOffset*/,
+                                  UErrorCode& status) const {
     if (U_FAILURE(status)) {
         return;
     }
     status = U_UNSUPPORTED_ERROR;
 }
 
+void BasicTimeZone::getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
+                                       int32_t& rawOffset, int32_t& dstOffset,
+                                       UErrorCode& status) const {
+    getOffsetFromLocal(date, (UTimeZoneLocalOption)nonExistingTimeOpt,
+                       (UTimeZoneLocalOption)duplicatedTimeOpt, rawOffset, dstOffset, status);
+}
+
 U_NAMESPACE_END
 
 #endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/i18n/olsontz.cpp b/icu4c/source/i18n/olsontz.cpp
index aa2ad7f..1d0fa80 100644
--- a/icu4c/source/i18n/olsontz.cpp
+++ b/icu4c/source/i18n/olsontz.cpp
@@ -399,9 +399,9 @@
     }
 }
 
-void
-OlsonTimeZone::getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
-                                  int32_t& rawoff, int32_t& dstoff, UErrorCode& ec) const {
+void OlsonTimeZone::getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingTimeOpt,
+                                       UTimeZoneLocalOption duplicatedTimeOpt,
+                                       int32_t& rawoff, int32_t& dstoff, UErrorCode& ec) const {
     if (U_FAILURE(ec)) {
         return;
     }
diff --git a/icu4c/source/i18n/olsontz.h b/icu4c/source/i18n/olsontz.h
index 275b1b4..f1d862f 100644
--- a/icu4c/source/i18n/olsontz.h
+++ b/icu4c/source/i18n/olsontz.h
@@ -187,8 +187,10 @@
     /**
      * BasicTimeZone API.
      */
-    virtual void getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
-        int32_t& rawoff, int32_t& dstoff, UErrorCode& ec) const;
+    virtual void getOffsetFromLocal(
+        UDate date, UTimeZoneLocalOption nonExistingTimeOpt,
+        UTimeZoneLocalOption duplicatedTimeOpt,
+        int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) const;
 
     /**
      * TimeZone API.  This method has no effect since objects of this
diff --git a/icu4c/source/i18n/rbtz.cpp b/icu4c/source/i18n/rbtz.cpp
index 3249a32..ddaa804 100644
--- a/icu4c/source/i18n/rbtz.cpp
+++ b/icu4c/source/i18n/rbtz.cpp
@@ -403,9 +403,9 @@
     getOffsetInternal(date, local, kFormer, kLatter, rawOffset, dstOffset, status);
 }
 
-void
-RuleBasedTimeZone::getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
-                                      int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) const {
+void RuleBasedTimeZone::getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingTimeOpt,
+                                           UTimeZoneLocalOption duplicatedTimeOpt,
+                                           int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) const {
     getOffsetInternal(date, TRUE, nonExistingTimeOpt, duplicatedTimeOpt, rawOffset, dstOffset, status);
 }
 
diff --git a/icu4c/source/i18n/simpletz.cpp b/icu4c/source/i18n/simpletz.cpp
index 0b1921e..02dafb7 100644
--- a/icu4c/source/i18n/simpletz.cpp
+++ b/icu4c/source/i18n/simpletz.cpp
@@ -509,8 +509,10 @@
 }
 
 void
-SimpleTimeZone::getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
-                                   int32_t& rawOffsetGMT, int32_t& savingsDST, UErrorCode& status) const {
+SimpleTimeZone::getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingTimeOpt,
+                                   UTimeZoneLocalOption duplicatedTimeOpt, int32_t& rawOffsetGMT,
+                                   int32_t& savingsDST, UErrorCode& status) const
+{
     if (U_FAILURE(status)) {
         return;
     }
diff --git a/icu4c/source/i18n/ucal.cpp b/icu4c/source/i18n/ucal.cpp
index c30ce09..099e9e3 100644
--- a/icu4c/source/i18n/ucal.cpp
+++ b/icu4c/source/i18n/ucal.cpp
@@ -828,4 +828,28 @@
     return resultLen;
 }
 
+U_CAPI void U_EXPORT2 ucal_getTimeZoneOffsetFromLocal(
+    const UCalendar* cal,
+    UTimeZoneLocalOption nonExistingTimeOpt,
+    UTimeZoneLocalOption duplicatedTimeOpt,
+    int32_t* rawOffset, int32_t* dstOffset, UErrorCode* status)
+{
+    if (U_FAILURE(*status)) {
+        return;
+    }
+    UDate date = ((Calendar*)cal)->getTime(*status);
+    if (U_FAILURE(*status)) {
+        return;
+    }
+    const TimeZone& tz = ((Calendar*)cal)->getTimeZone();
+    const BasicTimeZone* btz = dynamic_cast<const BasicTimeZone *>(&tz);
+    if (btz == nullptr) {
+        *status = U_ILLEGAL_ARGUMENT_ERROR;
+        return;
+    }
+    btz->getOffsetFromLocal(
+        date, nonExistingTimeOpt, duplicatedTimeOpt,
+        *rawOffset, *dstOffset, *status);
+}
+
 #endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/i18n/unicode/basictz.h b/icu4c/source/i18n/unicode/basictz.h
index 7dd981a..0c88d49 100644
--- a/icu4c/source/i18n/unicode/basictz.h
+++ b/icu4c/source/i18n/unicode/basictz.h
@@ -152,6 +152,17 @@
     virtual void getSimpleRulesNear(UDate date, InitialTimeZoneRule*& initial,
         AnnualTimeZoneRule*& std, AnnualTimeZoneRule*& dst, UErrorCode& status) const;
 
+#ifndef U_FORCE_HIDE_DRAFT_API
+    /**
+     * Get time zone offsets from local wall time.
+     * @draft ICU 69
+     */
+    virtual void getOffsetFromLocal(
+        UDate date, UTimeZoneLocalOption nonExistingTimeOpt,
+        UTimeZoneLocalOption duplicatedTimeOpt, int32_t& rawOffset,
+        int32_t& dstOffset, UErrorCode& status) const;
+
+#endif /* U_FORCE_HIDE_DRAFT_API */
 
 #ifndef U_HIDE_INTERNAL_API
     /**
@@ -161,8 +172,8 @@
     enum {
         kStandard = 0x01,
         kDaylight = 0x03,
-        kFormer = 0x04,
-        kLatter = 0x0C
+        kFormer = 0x04, /* UCAL_TZ_LOCAL_FORMER */
+        kLatter = 0x0C  /* UCAL_TZ_LOCAL_LATTER */
     };
 #endif  /* U_HIDE_INTERNAL_API */
 
@@ -170,7 +181,7 @@
      * Get time zone offsets from local wall time.
      * @internal
      */
-    virtual void getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
+    void getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
         int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) const;
 
 protected:
diff --git a/icu4c/source/i18n/unicode/rbtz.h b/icu4c/source/i18n/unicode/rbtz.h
index 06d566a..1509814 100644
--- a/icu4c/source/i18n/unicode/rbtz.h
+++ b/icu4c/source/i18n/unicode/rbtz.h
@@ -302,12 +302,16 @@
     virtual void getTimeZoneRules(const InitialTimeZoneRule*& initial,
         const TimeZoneRule* trsrules[], int32_t& trscount, UErrorCode& status) const;
 
+#ifndef U_FORCE_HIDE_DRAFT_API
     /**
      * Get time zone offsets from local wall time.
-     * @internal
+     * @draft ICU 69
      */
-    virtual void getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
+    virtual void getOffsetFromLocal(
+        UDate date, UTimeZoneLocalOption nonExistingTimeOpt,
+        UTimeZoneLocalOption duplicatedTimeOpt,
         int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) const;
+#endif /* U_FORCE_HIDE_DRAFT_API */
 
 private:
     void deleteRules(void);
diff --git a/icu4c/source/i18n/unicode/simpletz.h b/icu4c/source/i18n/unicode/simpletz.h
index 792fddb..f85b040 100644
--- a/icu4c/source/i18n/unicode/simpletz.h
+++ b/icu4c/source/i18n/unicode/simpletz.h
@@ -620,12 +620,16 @@
     virtual void getOffset(UDate date, UBool local, int32_t& rawOffset,
                            int32_t& dstOffset, UErrorCode& ec) const;
 
+#ifndef U_FORCE_HIDE_DRAFT_API
     /**
      * Get time zone offsets from local wall time.
-     * @internal
+     * @draft ICU 69
      */
-    virtual void getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
+    virtual void getOffsetFromLocal(
+        UDate date, UTimeZoneLocalOption nonExistingTimeOpt,
+        UTimeZoneLocalOption duplicatedTimeOpt,
         int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) const;
+#endif /* U_FORCE_HIDE_DRAFT_API */
 
     /**
      * Returns the TimeZone's raw GMT offset (i.e., the number of milliseconds to add
diff --git a/icu4c/source/i18n/unicode/ucal.h b/icu4c/source/i18n/unicode/ucal.h
index 89b1604..509adb2 100644
--- a/icu4c/source/i18n/unicode/ucal.h
+++ b/icu4c/source/i18n/unicode/ucal.h
@@ -1617,6 +1617,102 @@
 ucal_getTimeZoneIDForWindowsID(const UChar* winid, int32_t len, const char* region,
                                 UChar* id, int32_t idCapacity, UErrorCode* status);
 
+#ifndef U_FORCE_HIDE_DRAFT_API
+/**
+ * Options used by ucal_getTimeZoneOffsetFromLocal and BasicTimeZone::getOffsetFromLocal()
+ * to specify how to interpret an input time when it does not exist, or when it is ambiguous,
+ * around a time zone transition.
+ * @draft ICU 69
+ */
+enum UTimeZoneLocalOption {
+#ifndef U_HIDE_DRAFT_API
+    /**
+     * An input time is always interpreted as local time before
+     * a time zone transition.
+     * @draft ICU 69
+     */
+    UCAL_TZ_LOCAL_FORMER = 0x04,
+    /**
+     * An input time is always interpreted as local time after
+     * a time zone transition.
+     * @draft ICU 69
+     */
+    UCAL_TZ_LOCAL_LATTER = 0x0C,
+    /**
+     * An input time is interpreted as standard time when local
+     * time is switched to/from daylight saving time. When both
+     * sides of a time zone transition are standard time,
+     * or daylight saving time, the local time before the
+     * transition is used.
+     * @draft ICU 69
+     */
+    UCAL_TZ_LOCAL_STANDARD_FORMER = UCAL_TZ_LOCAL_FORMER | 0x01,
+    /**
+     * An input time is interpreted as standard time when local
+     * time is switched to/from daylight saving time. When both
+     * sides of a time zone transition are standard time,
+     * or daylight saving time, the local time after the
+     * transition is used.
+     * @draft ICU 69
+     */
+    UCAL_TZ_LOCAL_STANDARD_LATTER = UCAL_TZ_LOCAL_LATTER | 0x01,
+    /**
+     * An input time is interpreted as daylight saving time when
+     * local time is switched to/from standard time. When both
+     * sides of a time zone transition are standard time,
+     * or daylight saving time, the local time before the
+     * transition is used.
+     * @draft ICU 69
+     */
+    UCAL_TZ_LOCAL_DAYLIGHT_FORMER = UCAL_TZ_LOCAL_FORMER | 0x03,
+    /**
+     * An input time is interpreted as daylight saving time when
+     * local time is switched to/from standard time. When both
+     * sides of a time zone transition are standard time,
+     * or daylight saving time, the local time after the
+     * transition is used.
+     * @draft ICU 69
+     */
+    UCAL_TZ_LOCAL_DAYLIGHT_LATTER = UCAL_TZ_LOCAL_LATTER | 0x03,
+#endif /* U_HIDE_DRAFT_API */
+};
+typedef enum UTimeZoneLocalOption UTimeZoneLocalOption; /**< @draft ICU 69 */
+
+/**
+* Returns the time zone raw and GMT offset for the given moment
+* in time.  Upon return, local-millis = GMT-millis + rawOffset +
+* dstOffset.  All computations are performed in the proleptic
+* Gregorian calendar.
+*
+* @param cal The UCalendar which specify the local date and time value to query.
+* @param nonExistingTimeOpt The option to indicate how to interpret the date and
+* time in the calendar represent a local time that skipped at a positive time
+* zone transitions (e.g. when the daylight saving time starts or the time zone
+* offset is increased due to a time zone rule change).
+* @param duplicatedTimeOpt The option to indicate how to interpret the date and
+* time in the calendar represent a local time that repeating multiple times at a
+* negative time zone transition (e.g. when the daylight saving time ends or the
+* time zone offset is decreased due to a time zone rule change)
+* @param rawOffset output parameter to receive the raw offset, that
+* is, the offset not including DST adjustments.
+* If the status is set to one of the error code, the value set is unspecified.
+* @param dstOffset output parameter to receive the DST offset,
+* that is, the offset to be added to `rawOffset' to obtain the
+* total offset between local and GMT time. If DST is not in
+* effect, this value is zero; otherwise it is a positive value,
+* typically one hour.
+* If the status is set to one of the error code, the value set is unspecified.
+* @param status A pointer to a UErrorCode to receive any errors.
+* @draft ICU 69
+*/
+U_CAPI void U_EXPORT2
+ucal_getTimeZoneOffsetFromLocal(
+    const UCalendar* cal,
+    UTimeZoneLocalOption nonExistingTimeOpt,
+    UTimeZoneLocalOption duplicatedTimeOpt,
+    int32_t* rawOffset, int32_t* dstOffset, UErrorCode* status);
+#endif /* U_FORCE_HIDE_DRAFT_API */
+
 #endif /* #if !UCONFIG_NO_FORMATTING */
 
 #endif
diff --git a/icu4c/source/i18n/unicode/vtzone.h b/icu4c/source/i18n/unicode/vtzone.h
index 383b8da..bb7a5a8 100644
--- a/icu4c/source/i18n/unicode/vtzone.h
+++ b/icu4c/source/i18n/unicode/vtzone.h
@@ -264,6 +264,17 @@
     virtual void getOffset(UDate date, UBool local, int32_t& rawOffset,
                            int32_t& dstOffset, UErrorCode& ec) const;
 
+ #ifndef U_FORCE_HIDE_DRAFT_API
+    /**
+     * Get time zone offsets from local wall time.
+     * @draft ICU 69
+     */
+    virtual void getOffsetFromLocal(
+        UDate date, UTimeZoneLocalOption nonExistingTimeOpt,
+        UTimeZoneLocalOption duplicatedTimeOpt,
+        int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) const;
+#endif /* U_FORCE_HIDE_DRAFT_API */
+
     /**
      * Sets the TimeZone's raw GMT offset (i.e., the number of milliseconds to add
      * to GMT to get local time, before taking daylight savings time into account).
diff --git a/icu4c/source/i18n/vtzone.cpp b/icu4c/source/i18n/vtzone.cpp
index 8b9fdec..2df9116 100644
--- a/icu4c/source/i18n/vtzone.cpp
+++ b/icu4c/source/i18n/vtzone.cpp
@@ -1217,6 +1217,12 @@
     return tz->getOffset(date, local, rawOffset, dstOffset, status);
 }
 
+void VTimeZone::getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingTimeOpt,
+                                   UTimeZoneLocalOption duplicatedTimeOpt,
+                                   int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) const {
+    tz->getOffsetFromLocal(date, nonExistingTimeOpt, duplicatedTimeOpt, rawOffset, dstOffset, status);
+}
+
 void
 VTimeZone::setRawOffset(int32_t offsetMillis) {
     tz->setRawOffset(offsetMillis);
diff --git a/icu4c/source/test/cintltst/ccaltst.c b/icu4c/source/test/cintltst/ccaltst.c
index da7d1a6..13bede2 100644
--- a/icu4c/source/test/cintltst/ccaltst.c
+++ b/icu4c/source/test/cintltst/ccaltst.c
@@ -41,6 +41,7 @@
 void TestGetTimeZoneIDByWindowsID(void);
 void TestJpnCalAddSetNextEra(void);
 void TestUcalOpenBufferRead(void);
+void TestGetTimeZoneOffsetFromLocal(void);
 
 void addCalTest(TestNode** root);
 
@@ -65,6 +66,7 @@
     addTest(root, &TestGetTimeZoneIDByWindowsID, "tsformat/ccaltst/TestGetTimeZoneIDByWindowsID");
     addTest(root, &TestJpnCalAddSetNextEra, "tsformat/ccaltst/TestJpnCalAddSetNextEra");
     addTest(root, &TestUcalOpenBufferRead, "tsformat/ccaltst/TestUcalOpenBufferRead");
+    addTest(root, &TestGetTimeZoneOffsetFromLocal, "tsformat/ccaltst/TestGetTimeZoneOffsetFromLocal");
 }
 
 /* "GMT" */
@@ -2553,4 +2555,220 @@
     ucal_close(cal);
 }
 
+
+/*
+ * Testing ucal_getTimeZoneOffsetFromLocal
+ */
+void
+TestGetTimeZoneOffsetFromLocal() {
+    static const UChar utc[] = u"Etc/GMT";
+
+    const int32_t HOUR = 60*60*1000;
+    const int32_t MINUTE = 60*1000;
+
+    const int32_t DATES[][6] = {
+        {2006, UCAL_APRIL, 2, 1, 30, 1*HOUR+30*MINUTE},
+        {2006, UCAL_APRIL, 2, 2, 00, 2*HOUR},
+        {2006, UCAL_APRIL, 2, 2, 30, 2*HOUR+30*MINUTE},
+        {2006, UCAL_APRIL, 2, 3, 00, 3*HOUR},
+        {2006, UCAL_APRIL, 2, 3, 30, 3*HOUR+30*MINUTE},
+        {2006, UCAL_OCTOBER, 29, 0, 30, 0*HOUR+30*MINUTE},
+        {2006, UCAL_OCTOBER, 29, 1, 00, 1*HOUR},
+        {2006, UCAL_OCTOBER, 29, 1, 30, 1*HOUR+30*MINUTE},
+        {2006, UCAL_OCTOBER, 29, 2, 00, 2*HOUR},
+        {2006, UCAL_OCTOBER, 29, 2, 30, 2*HOUR+30*MINUTE},
+    };
+
+    // Expected offsets by
+    // void U_ucal_getTimeZoneOffsetFromLocal(
+    //   const UCalendar* cal,
+    //   UTimeZoneLocalOption nonExistingTimeOpt,
+    //   UTimeZoneLocalOption duplicatedTimeOpt,
+    //   int32_t* rawOffset, int32_t* dstOffset, UErrorCode* status);
+    // with nonExistingTimeOpt=UCAL_TZ_LOCAL_STANDARD and
+    // duplicatedTimeOpt=UCAL_TZ_LOCAL_STANDARD
+    const int32_t OFFSETS2[][2] = {
+        // April 2, 2006
+        {-8*HOUR, 0},
+        {-8*HOUR, 0},
+        {-8*HOUR, 0},
+        {-8*HOUR, 1*HOUR},
+        {-8*HOUR, 1*HOUR},
+
+        // Oct 29, 2006
+        {-8*HOUR, 1*HOUR},
+        {-8*HOUR, 0},
+        {-8*HOUR, 0},
+        {-8*HOUR, 0},
+        {-8*HOUR, 0},
+    };
+
+    // Expected offsets by
+    // void U_ucal_getTimeZoneOffsetFromLocal(
+    //   const UCalendar* cal,
+    //   UTimeZoneLocalOption nonExistingTimeOpt,
+    //   UTimeZoneLocalOption duplicatedTimeOpt,
+    //   int32_t* rawOffset, int32_t* dstOffset, UErrorCode* status);
+    // with nonExistingTimeOpt=UCAL_TZ_LOCAL_DAYLIGHT and
+    // duplicatedTimeOpt=UCAL_TZ_LOCAL_DAYLIGHT
+    const int32_t OFFSETS3[][2] = {
+        // April 2, 2006
+        {-8*HOUR, 0},
+        {-8*HOUR, 1*HOUR},
+        {-8*HOUR, 1*HOUR},
+        {-8*HOUR, 1*HOUR},
+        {-8*HOUR, 1*HOUR},
+
+        // October 29, 2006
+        {-8*HOUR, 1*HOUR},
+        {-8*HOUR, 1*HOUR},
+        {-8*HOUR, 1*HOUR},
+        {-8*HOUR, 0},
+        {-8*HOUR, 0},
+    };
+
+    UErrorCode status = U_ZERO_ERROR;
+
+    int32_t rawOffset, dstOffset;
+    UCalendar *cal = ucal_open(utc, -1, "en", UCAL_GREGORIAN, &status);
+    if (U_FAILURE(status)) {
+        log_data_err("ucal_open: %s", u_errorName(status));
+        return;
+    }
+
+    // Calculate millis
+    UDate MILLIS[UPRV_LENGTHOF(DATES)];
+    for (int32_t i = 0; i < UPRV_LENGTHOF(DATES); i++) {
+        ucal_setDateTime(cal, DATES[i][0], DATES[i][1], DATES[i][2],
+                         DATES[i][3], DATES[i][4], 0, &status);
+        MILLIS[i] = ucal_getMillis(cal, &status);
+        if (U_FAILURE(status)) {
+            log_data_err("ucal_getMillis failed");
+            return;
+        }
+    }
+    ucal_setTimeZone(cal, AMERICA_LOS_ANGELES, -1, &status);
+
+    // Test void ucal_getTimeZoneOffsetFromLocal(
+    // const UCalendar* cal,
+    // UTimeZoneLocalOption nonExistingTimeOpt,
+    // UTimeZoneLocalOption duplicatedTimeOpt,
+    // int32_t* rawOffset, int32_t* dstOffset, UErrorCode* status);
+    // with nonExistingTimeOpt=UCAL_TZ_LOCAL_STANDARD and
+    // duplicatedTimeOpt=UCAL_TZ_LOCAL_STANDARD
+    for (int m = 0; m < UPRV_LENGTHOF(DATES); m++) {
+        status = U_ZERO_ERROR;
+        ucal_setMillis(cal, MILLIS[m], &status);
+        if (U_FAILURE(status)) {
+            log_data_err("ucal_setMillis: %s\n", u_errorName(status));
+        }
+
+        ucal_getTimeZoneOffsetFromLocal(cal, UCAL_TZ_LOCAL_STANDARD_FORMER, UCAL_TZ_LOCAL_STANDARD_LATTER,
+            &rawOffset, &dstOffset, &status);
+        if (U_FAILURE(status)) {
+            log_err("ERROR: ucal_getTimeZoneOffsetFromLocal((%d-%d-%d %d:%d:0),"
+                    "UCAL_TZ_LOCAL_STANDARD_FORMER, UCAL_TZ_LOCAL_STANDARD_LATTER: %s\n",
+                    DATES[m][0], DATES[m][1], DATES[m][2], DATES[m][3], DATES[m][4],
+                    u_errorName(status));
+        } else if (rawOffset != OFFSETS2[m][0] || dstOffset != OFFSETS2[m][1]) {
+            log_err("Bad offset returned at (%d-%d-%d %d:%d:0) "
+                    "(wall/UCAL_TZ_LOCAL_STANDARD_FORMER/UCAL_TZ_LOCAL_STANDARD_LATTER) \n- Got: %d / %d "
+                    " Expected %d / %d\n",
+                    DATES[m][0], DATES[m][1], DATES[m][2], DATES[m][3], DATES[m][4],
+                    rawOffset, dstOffset, OFFSETS2[m][0], OFFSETS2[m][1]);
+        }
+    }
+
+    // Test void ucal_getTimeZoneOffsetFromLocal(
+    // const UCalendar* cal,
+    // UTimeZoneLocalOption nonExistingTimeOpt,
+    // UTimeZoneLocalOption duplicatedTimeOpt,
+    // int32_t* rawOffset, int32_t* dstOffset, UErrorCode* status);
+    // with nonExistingTimeOpt=UCAL_TZ_LOCAL_DAYLIGHT and
+    // duplicatedTimeOpt=UCAL_TZ_LOCAL_DAYLIGHT
+    for (int m = 0; m < UPRV_LENGTHOF(DATES); m++) {
+        status = U_ZERO_ERROR;
+        ucal_setMillis(cal, MILLIS[m], &status);
+        if (U_FAILURE(status)) {
+            log_data_err("ucal_setMillis: %s\n", u_errorName(status));
+        }
+
+        ucal_getTimeZoneOffsetFromLocal(cal, UCAL_TZ_LOCAL_DAYLIGHT_LATTER, UCAL_TZ_LOCAL_DAYLIGHT_FORMER,
+            &rawOffset, &dstOffset, &status);
+        if (U_FAILURE(status)) {
+            log_err("ERROR: ucal_getTimeZoneOffsetFromLocal((%d-%d-%d %d:%d:0),"
+                    "UCAL_TZ_LOCAL_DAYLIGHT_LATTER, UCAL_TZ_LOCAL_DAYLIGHT_FORMER: %s\n",
+                    DATES[m][0], DATES[m][1], DATES[m][2], DATES[m][3], DATES[m][4],
+                    u_errorName(status));
+        } else if (rawOffset != OFFSETS3[m][0] || dstOffset != OFFSETS3[m][1]) {
+            log_err("Bad offset returned at (%d-%d-%d %d:%d:0) "
+                    "(wall/UCAL_TZ_LOCAL_DAYLIGHT_LATTER/UCAL_TZ_LOCAL_DAYLIGHT_FORMER) \n- Got: %d / %d "
+                    " Expected %d / %d\n",
+                    DATES[m][0], DATES[m][1], DATES[m][2], DATES[m][3], DATES[m][4],
+                    rawOffset, dstOffset, OFFSETS3[m][0], OFFSETS3[m][1]);
+        }
+    }
+
+    // Test void ucal_getTimeZoneOffsetFromLocal(
+    // const UCalendar* cal,
+    // UTimeZoneLocalOption nonExistingTimeOpt,
+    // UTimeZoneLocalOption duplicatedTimeOpt,
+    // int32_t* rawOffset, int32_t* dstOffset, UErrorCode* status);
+    // with nonExistingTimeOpt=UCAL_TZ_LOCAL_FORMER and
+    // duplicatedTimeOpt=UCAL_TZ_LOCAL_LATTER
+    for (int m = 0; m < UPRV_LENGTHOF(DATES); m++) {
+        status = U_ZERO_ERROR;
+        ucal_setMillis(cal, MILLIS[m], &status);
+        if (U_FAILURE(status)) {
+            log_data_err("ucal_setMillis: %s\n", u_errorName(status));
+        }
+
+        ucal_getTimeZoneOffsetFromLocal(cal, UCAL_TZ_LOCAL_FORMER, UCAL_TZ_LOCAL_LATTER,
+            &rawOffset, &dstOffset, &status);
+        if (U_FAILURE(status)) {
+            log_err("ERROR: ucal_getTimeZoneOffsetFromLocal((%d-%d-%d %d:%d:0),"
+                    "UCAL_TZ_LOCAL_FORMER, UCAL_TZ_LOCAL_LATTER: %s\n",
+                    DATES[m][0], DATES[m][1], DATES[m][2], DATES[m][3], DATES[m][4],
+                    u_errorName(status));
+        } else if (rawOffset != OFFSETS2[m][0] || dstOffset != OFFSETS2[m][1]) {
+            log_err("Bad offset returned at (%d-%d-%d %d:%d:0) "
+                    "(wall/UCAL_TZ_LOCAL_FORMER/UCAL_TZ_LOCAL_LATTER) \n- Got: %d / %d "
+                    " Expected %d / %d\n",
+                    DATES[m][0], DATES[m][1], DATES[m][2], DATES[m][3], DATES[m][4],
+                    rawOffset, dstOffset, OFFSETS2[m][0], OFFSETS2[m][1]);
+        }
+    }
+
+    // Test void ucal_getTimeZoneOffsetFromLocal(
+    // const UCalendar* cal,
+    // UTimeZoneLocalOption nonExistingTimeOpt,
+    // UTimeZoneLocalOption duplicatedTimeOpt,
+    // int32_t* rawOffset, int32_t* dstOffset, UErrorCode* status);
+    // with nonExistingTimeOpt=UCAL_TZ_LOCAL_LATTER and
+    // duplicatedTimeOpt=UCAL_TZ_LOCAL_FORMER
+    for (int m = 0; m < UPRV_LENGTHOF(DATES); m++) {
+        status = U_ZERO_ERROR;
+        ucal_setMillis(cal, MILLIS[m], &status);
+        if (U_FAILURE(status)) {
+            log_data_err("ucal_setMillis: %s\n", u_errorName(status));
+        }
+
+        ucal_getTimeZoneOffsetFromLocal(cal, UCAL_TZ_LOCAL_LATTER, UCAL_TZ_LOCAL_FORMER,
+            &rawOffset, &dstOffset, &status);
+        if (U_FAILURE(status)) {
+            log_err("ERROR: ucal_getTimeZoneOffsetFromLocal((%d-%d-%d %d:%d:0),"
+                    "UCAL_TZ_LOCAL_LATTER, UCAL_TZ_LOCAL_FORMER: %s\n",
+                    DATES[m][0], DATES[m][1], DATES[m][2], DATES[m][3], DATES[m][4],
+                    u_errorName(status));
+        } else if (rawOffset != OFFSETS3[m][0] || dstOffset != OFFSETS3[m][1]) {
+            log_err("Bad offset returned at (%d-%d-%d %d:%d:0) "
+                    "(wall/UCAL_TZ_LOCAL_LATTER/UCAL_TZ_LOCAL_FORMER) \n- Got: %d / %d "
+                    " Expected %d / %d\n",
+                    DATES[m][0], DATES[m][1], DATES[m][2], DATES[m][3], DATES[m][4],
+                    rawOffset, dstOffset, OFFSETS3[m][0], OFFSETS3[m][1]);
+        }
+    }
+    ucal_close(cal);
+}
+
 #endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/test/intltest/tzoffloc.cpp b/icu4c/source/test/intltest/tzoffloc.cpp
index 1402469..d567cb9 100644
--- a/icu4c/source/test/intltest/tzoffloc.cpp
+++ b/icu4c/source/test/intltest/tzoffloc.cpp
@@ -79,9 +79,9 @@
 
     // Expected offsets by void getOffset(UDate date, UBool local, int32_t& rawOffset,
     // int32_t& dstOffset, UErrorCode& ec) with local=TRUE
-    // or void getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
+    // or void getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingTimeOpt, UTimeZoneLocalOption duplicatedTimeOpt,
     // int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) with
-    // nonExistingTimeOpt=kStandard/duplicatedTimeOpt=kStandard
+    // nonExistingTimeOpt=STANDARD_*/duplicatedTimeOpt=STANDARD_*
     const int32_t OFFSETS2[NUM_DATES][2] = {
         // April 2, 2006
         {-8*HOUR, 0},
@@ -98,9 +98,9 @@
         {-8*HOUR, 0},
     };
 
-    // Expected offsets by void getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt,
-    // int32_t duplicatedTimeOpt, int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) with
-    // nonExistingTimeOpt=kDaylight/duplicatedTimeOpt=kDaylight
+    // Expected offsets by void getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingTimeOpt,
+    // UTimeZoneLocalOption duplicatedTimeOpt, int32_t& rawOffset, int32_t& dstOffset, UErrorCode& status) with
+    // nonExistingTimeOpt=DAYLIGHT_*/duplicatedTimeOpt=DAYLIGHT_*
     const int32_t OFFSETS3[][2] = {
         // April 2, 2006
         {-8*HOUR, 0},
@@ -237,84 +237,84 @@
         }
     }
 
-    // Test getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
+    // Test getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingTimeOpt, UTimeZoneLocalOption duplicatedTimeOpt,
     // int32_t& rawOffset, int32_t& dstOffset, UErroCode& status)
-    // with nonExistingTimeOpt=kStandard/duplicatedTimeOpt=kStandard
+    // with nonExistingTimeOpt=STANDARD_FORMER/duplicatedTimeOpt=STANDARD_LATTER
     for (int32_t i = 0; i < NUM_TIMEZONES; i++) {
         for (int m = 0; m < NUM_DATES; m++) {
             status = U_ZERO_ERROR;
-            TESTZONES[i]->getOffsetFromLocal(MILLIS[m], BasicTimeZone::kStandard, BasicTimeZone::kStandard,
+            TESTZONES[i]->getOffsetFromLocal(MILLIS[m], UCAL_TZ_LOCAL_STANDARD_FORMER, UCAL_TZ_LOCAL_STANDARD_LATTER,
                 rawOffset, dstOffset, status);
             if (U_FAILURE(status)) {
-                errln((UnicodeString)"getOffsetFromLocal with kStandard/kStandard failed for TESTZONES[" + i + "]");
+                errln((UnicodeString)"getOffsetFromLocal with UCAL_TZ_LOCAL_STANDARD_FORMER/UCAL_TZ_LOCAL_STANDARD_LATTER failed for TESTZONES[" + i + "]");
             } else if (rawOffset != OFFSETS2[m][0] || dstOffset != OFFSETS2[m][1]) {
                 dateStr.remove();
                 df.format(MILLIS[m], dateStr);
                 dataerrln((UnicodeString)"Bad offset returned by TESTZONES[" + i + "] at "
-                        + dateStr + "(wall/kStandard/kStandard) - Got: "
+                        + dateStr + "(wall/STANDARD_FORMER/STANDARD_LATTER) - Got: "
                         + rawOffset + "/" + dstOffset
                         + " Expected: " + OFFSETS2[m][0] + "/" + OFFSETS2[m][1]);
             }
         }
     }
 
-    // Test getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
+    // Test getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingTimeOpt, UTimeZoneLocalOption duplicatedTimeOpt,
     // int32_t& rawOffset, int32_t& dstOffset, UErroCode& status)
-    // with nonExistingTimeOpt=kDaylight/duplicatedTimeOpt=kDaylight
+    // with nonExistingTimeOpt=DAYLIGHT_LATTER/duplicatedTimeOpt=DAYLIGHT_FORMER
     for (int32_t i = 0; i < NUM_TIMEZONES; i++) {
         for (int m = 0; m < NUM_DATES; m++) {
             status = U_ZERO_ERROR;
-            TESTZONES[i]->getOffsetFromLocal(MILLIS[m], BasicTimeZone::kDaylight, BasicTimeZone::kDaylight,
+            TESTZONES[i]->getOffsetFromLocal(MILLIS[m], UCAL_TZ_LOCAL_DAYLIGHT_LATTER, UCAL_TZ_LOCAL_DAYLIGHT_FORMER,
                 rawOffset, dstOffset, status);
             if (U_FAILURE(status)) {
-                errln((UnicodeString)"getOffsetFromLocal with kDaylight/kDaylight failed for TESTZONES[" + i + "]");
+                errln((UnicodeString)"getOffsetFromLocal with UCAL_TZ_LOCAL_DAYLIGHT_LATTER/UCAL_TZ_LOCAL_DAYLIGHT_FORMER failed for TESTZONES[" + i + "]");
             } else if (rawOffset != OFFSETS3[m][0] || dstOffset != OFFSETS3[m][1]) {
                 dateStr.remove();
                 df.format(MILLIS[m], dateStr);
                 dataerrln((UnicodeString)"Bad offset returned by TESTZONES[" + i + "] at "
-                        + dateStr + "(wall/kDaylight/kDaylight) - Got: "
+                        + dateStr + "(wall/DAYLIGHT_LATTER/DAYLIGHT_FORMER) - Got: "
                         + rawOffset + "/" + dstOffset
                         + " Expected: " + OFFSETS3[m][0] + "/" + OFFSETS3[m][1]);
             }
         }
     }
 
-    // Test getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
+    // Test getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingTimeOpt, UTimeZoneLocalOption duplicatedTimeOpt,
     // int32_t& rawOffset, int32_t& dstOffset, UErroCode& status)
-    // with nonExistingTimeOpt=kFormer/duplicatedTimeOpt=kLatter
+    // with nonExistingTimeOpt=FORMER/duplicatedTimeOpt=LATTER
     for (int32_t i = 0; i < NUM_TIMEZONES; i++) {
         for (int m = 0; m < NUM_DATES; m++) {
             status = U_ZERO_ERROR;
-            TESTZONES[i]->getOffsetFromLocal(MILLIS[m], BasicTimeZone::kFormer, BasicTimeZone::kLatter,
+            TESTZONES[i]->getOffsetFromLocal(MILLIS[m], UCAL_TZ_LOCAL_FORMER, UCAL_TZ_LOCAL_LATTER,
                 rawOffset, dstOffset, status);
             if (U_FAILURE(status)) {
-                errln((UnicodeString)"getOffsetFromLocal with kFormer/kLatter failed for TESTZONES[" + i + "]");
+                errln((UnicodeString)"getOffsetFromLocal with UCAL_TZ_LOCAL_FORMER/UCAL_TZ_LOCAL_LATTER failed for TESTZONES[" + i + "]");
             } else if (rawOffset != OFFSETS2[m][0] || dstOffset != OFFSETS2[m][1]) {
                 dateStr.remove();
                 df.format(MILLIS[m], dateStr);
                 dataerrln((UnicodeString)"Bad offset returned by TESTZONES[" + i + "] at "
-                        + dateStr + "(wall/kFormer/kLatter) - Got: "
+                        + dateStr + "(wall/FORMER/LATTER) - Got: "
                         + rawOffset + "/" + dstOffset
                         + " Expected: " + OFFSETS2[m][0] + "/" + OFFSETS2[m][1]);
             }
         }
     }
 
-    // Test getOffsetFromLocal(UDate date, int32_t nonExistingTimeOpt, int32_t duplicatedTimeOpt,
+    // Test getOffsetFromLocal(UDate date, UTimeZoneLocalOption nonExistingTimeOpt, UTimeZoneLocalOption duplicatedTimeOpt,
     // int32_t& rawOffset, int32_t& dstOffset, UErroCode& status)
-    // with nonExistingTimeOpt=kLatter/duplicatedTimeOpt=kFormer
+    // with nonExistingTimeOpt=LATTER/duplicatedTimeOpt=FORMER
     for (int32_t i = 0; i < NUM_TIMEZONES; i++) {
         for (int m = 0; m < NUM_DATES; m++) {
             status = U_ZERO_ERROR;
-            TESTZONES[i]->getOffsetFromLocal(MILLIS[m], BasicTimeZone::kLatter, BasicTimeZone::kFormer,
+            TESTZONES[i]->getOffsetFromLocal(MILLIS[m], UCAL_TZ_LOCAL_LATTER, UCAL_TZ_LOCAL_FORMER,
                 rawOffset, dstOffset, status);
             if (U_FAILURE(status)) {
-                errln((UnicodeString)"getOffsetFromLocal with kLatter/kFormer failed for TESTZONES[" + i + "]");
+                errln((UnicodeString)"getOffsetFromLocal with UCAL_TZ_LOCAL_LATTER/UCAL_TZ_LOCAL_FORMER failed for TESTZONES[" + i + "]");
             } else if (rawOffset != OFFSETS3[m][0] || dstOffset != OFFSETS3[m][1]) {
                 dateStr.remove();
                 df.format(MILLIS[m], dateStr);
                 dataerrln((UnicodeString)"Bad offset returned by TESTZONES[" + i + "] at "
-                        + dateStr + "(wall/kLatter/kFormer) - Got: "
+                        + dateStr + "(wall/LATTER/FORMER) - Got: "
                         + rawOffset + "/" + dstOffset
                         + " Expected: " + OFFSETS3[m][0] + "/" + OFFSETS3[m][1]);
             }