/** * \file * Unity icall support for Mono. * * Copyright 2016 Unity * Licensed under the MIT license. See LICENSE file in the project root for full license information. */ #include #include #include "mono/utils/mono-compiler.h" #include #include #include #include #include #include #ifdef HAVE_SYS_TIME_H #include #endif /* * Magic number to convert a time which is relative to * Jan 1, 1970 into a value which is relative to Jan 1, 0001. */ #define EPOCH_ADJUST ((guint64)62135596800LL) /* * Magic number to convert FILETIME base Jan 1, 1601 to DateTime - base Jan, 1, 0001 */ #define FILETIME_ADJUST ((guint64)504911232000000000LL) #ifdef PLATFORM_WIN32 /* convert a SYSTEMTIME which is of the form "last thursday in october" to a real date */ static void convert_to_absolute_date(SYSTEMTIME *date) { #define IS_LEAP(y) ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) static int days_in_month[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; static int leap_days_in_month[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; /* from the calendar FAQ */ int a = (14 - date->wMonth) / 12; int y = date->wYear - a; int m = date->wMonth + 12 * a - 2; int d = (1 + y + y/4 - y/100 + y/400 + (31*m)/12) % 7; /* d is now the day of the week for the first of the month (0 == Sunday) */ int day_of_week = date->wDayOfWeek; /* set day_in_month to the first day in the month which falls on day_of_week */ int day_in_month = 1 + (day_of_week - d); if (day_in_month <= 0) day_in_month += 7; /* wDay is 1 for first weekday in month, 2 for 2nd ... 5 means last - so work that out allowing for days in the month */ date->wDay = day_in_month + (date->wDay - 1) * 7; if (date->wDay > (IS_LEAP(date->wYear) ? leap_days_in_month[date->wMonth - 1] : days_in_month[date->wMonth - 1])) date->wDay -= 7; } #endif #ifndef PLATFORM_WIN32 /* * Return's the offset from GMT of a local time. * * tm is a local time * t is the same local time as seconds. */ static int gmt_offset(struct tm *tm, time_t t) { #if defined (HAVE_TM_GMTOFF) return tm->tm_gmtoff; #else struct tm g; time_t t2; g = *gmtime(&t); g.tm_isdst = tm->tm_isdst; t2 = mktime(&g); return (int)difftime(t, t2); #endif } #endif /* * This is heavily based on zdump.c from glibc 2.2. * * * data[0]: start of daylight saving time (in DateTime ticks). * * data[1]: end of daylight saving time (in DateTime ticks). * * data[2]: utcoffset (in TimeSpan ticks). * * data[3]: additional offset when daylight saving (in TimeSpan ticks). * * name[0]: name of this timezone when not daylight saving. * * name[1]: name of this timezone when daylight saving. * * FIXME: This only works with "standard" Unix dates (years between 1900 and 2100) while * the class library allows years between 1 and 9999. * * Returns true on success and zero on failure. */ guint32 ves_icall_System_CurrentSystemTimeZone_GetTimeZoneData (guint32 year, MonoArray **data, MonoArray **names, MonoBoolean *daylight_inverted) { MonoError error; #ifndef PLATFORM_WIN32 MonoDomain *domain = mono_domain_get (); struct tm start, tt; time_t t; long int gmtoff_start; long int gmtoff; int is_transitioned = 0, day; char tzone [64]; MONO_CHECK_ARG_NULL (data, FALSE); MONO_CHECK_ARG_NULL (names, FALSE); mono_gc_wbarrier_generic_store (data, (MonoObject*) mono_array_new_checked (domain, mono_defaults.int64_class, 4, &error)); mono_gc_wbarrier_generic_store (names, (MonoObject*) mono_array_new_checked (domain, mono_defaults.string_class, 2, &error)); /* * no info is better than crashing: we'll need our own tz data * to make this work properly, anyway. The range is probably * reduced to 1970 .. 2037 because that is what mktime is * guaranteed to support (we get into an infinite loop * otherwise). */ memset (&start, 0, sizeof (start)); start.tm_mday = 1; start.tm_year = year-1900; t = mktime (&start); if ((year < 1970) || (year > 2037) || (t == -1)) { t = time (NULL); tt = *localtime (&t); strftime (tzone, sizeof (tzone), "%Z", &tt); mono_array_setref ((*names), 0, mono_string_new_checked (domain, tzone, &error)); mono_array_setref ((*names), 1, mono_string_new_checked (domain, tzone, &error)); *daylight_inverted = 0; return 1; } *daylight_inverted = start.tm_isdst; gmtoff = gmt_offset (&start, t); gmtoff_start = gmtoff; /* For each day of the year, calculate the tm_gmtoff. */ for (day = 0; day < 365; day++) { t += 3600*24; tt = *localtime (&t); /* Daylight saving starts or ends here. */ if (gmt_offset (&tt, t) != gmtoff) { struct tm tt1; time_t t1; /* Try to find the exact hour when daylight saving starts/ends. */ t1 = t; do { t1 -= 3600; tt1 = *localtime (&t1); } while (gmt_offset (&tt1, t1) != gmtoff); /* Try to find the exact minute when daylight saving starts/ends. */ do { t1 += 60; tt1 = *localtime (&t1); } while (gmt_offset (&tt1, t1) == gmtoff); t1+=gmtoff; strftime (tzone, sizeof (tzone), "%Z", &tt); /* Write data, if we're already in daylight saving, we're done. */ if (is_transitioned) { if (!start.tm_isdst) mono_array_setref ((*names), 0, mono_string_new_checked (domain, tzone, &error)); else mono_array_setref ((*names), 1, mono_string_new_checked (domain, tzone, &error)); mono_array_set ((*data), gint64, 1, ((gint64)t1 + EPOCH_ADJUST) * 10000000L); return 1; } else { if (!start.tm_isdst) mono_array_setref ((*names), 1, mono_string_new_checked (domain, tzone, &error)); else mono_array_setref ((*names), 0, mono_string_new_checked (domain, tzone, &error)); mono_array_set ((*data), gint64, 0, ((gint64)t1 + EPOCH_ADJUST) * 10000000L); is_transitioned = 1; } /* This is only set once when we enter daylight saving. */ if (*daylight_inverted == 0) { mono_array_set ((*data), gint64, 2, (gint64)gmtoff * 10000000L); mono_array_set ((*data), gint64, 3, (gint64)(gmt_offset (&tt, t) - gmtoff) * 10000000L); } else { mono_array_set ((*data), gint64, 2, (gint64)(gmtoff_start + (gmt_offset (&tt, t) - gmtoff)) * 10000000L); mono_array_set ((*data), gint64, 3, (gint64)(gmtoff - gmt_offset (&tt, t)) * 10000000L); } gmtoff = gmt_offset (&tt, t); } } if (!is_transitioned) { strftime (tzone, sizeof (tzone), "%Z", &tt); mono_array_setref ((*names), 0, mono_string_new_checked (domain, tzone, &error)); mono_array_setref ((*names), 1, mono_string_new_checked (domain, tzone, &error)); mono_array_set ((*data), gint64, 0, 0); mono_array_set ((*data), gint64, 1, 0); mono_array_set ((*data), gint64, 2, (gint64) gmtoff * 10000000L); mono_array_set ((*data), gint64, 3, 0); *daylight_inverted = 0; } return 1; #else //On Windows, we should always load timezones in managed return 0; #endif }