/*
 * localtime_r.c
 * Original Author: Adapted from tzcode maintained by Arthur David Olson.
 * Modifications:
 * - Changed to mktm_r and added __tzcalc_limits - 04/10/02, Jeff Johnston
 * - Fixed bug in mday computations - 08/12/04, Alex Mogilnikov <alx@intellectronika.ru>
 * - Fixed bug in __tzcalc_limits - 08/12/04, Alex Mogilnikov <alx@intellectronika.ru>
 * - Implement localtime_r() with gmtime_r() and the conditional code moved
 *   from _mktm_r() - 05/09/14, Freddie Chopin <freddie_chopin@op.pl>
 *
 * Converts the calendar time pointed to by tim_p into a broken-down time
 * expressed as local time. Returns a pointer to a structure containing the
 * broken-down time.
 */

#include "local.h"

struct tm *
localtime_r (const time_t *__restrict tim_p,
	struct tm *__restrict res)
{
  long offset;
  int hours, mins, secs;
  int year;
  __tzinfo_type *const tz = __gettzinfo ();
  const int *ip;

  res = gmtime_r (tim_p, res);

  year = res->tm_year + YEAR_BASE;
  ip = __month_lengths[isleap(year)];

  TZ_LOCK;
  _tzset_unlocked ();
  if (_daylight)
    {
      if (year == tz->__tzyear || __tzcalc_limits (year))
	res->tm_isdst = (tz->__tznorth
	  ? (*tim_p >= tz->__tzrule[0].change
	  && *tim_p < tz->__tzrule[1].change)
	  : (*tim_p >= tz->__tzrule[0].change
	  || *tim_p < tz->__tzrule[1].change));
      else
	res->tm_isdst = -1;
    }
  else
    res->tm_isdst = 0;

  offset = (res->tm_isdst == 1
    ? tz->__tzrule[1].offset
    : tz->__tzrule[0].offset);

  hours = (int) (offset / SECSPERHOUR);
  offset = offset % SECSPERHOUR;

  mins = (int) (offset / SECSPERMIN);
  secs = (int) (offset % SECSPERMIN);

  res->tm_sec -= secs;
  res->tm_min -= mins;
  res->tm_hour -= hours;

  if (res->tm_sec >= SECSPERMIN)
    {
      res->tm_min += 1;
      res->tm_sec -= SECSPERMIN;
    }
  else if (res->tm_sec < 0)
    {
      res->tm_min -= 1;
      res->tm_sec += SECSPERMIN;
    }
  if (res->tm_min >= MINSPERHOUR)
    {
      res->tm_hour += 1;
      res->tm_min -= MINSPERHOUR;
    }
  else if (res->tm_min < 0)
    {
      res->tm_hour -= 1;
      res->tm_min += MINSPERHOUR;
    }
  if (res->tm_hour >= HOURSPERDAY)
    {
      ++res->tm_yday;
      ++res->tm_wday;
      if (res->tm_wday > 6)
	res->tm_wday = 0;
      ++res->tm_mday;
      res->tm_hour -= HOURSPERDAY;
      if (res->tm_mday > ip[res->tm_mon])
	{
	  res->tm_mday -= ip[res->tm_mon];
	  res->tm_mon += 1;
	  if (res->tm_mon == 12)
	    {
	      res->tm_mon = 0;
	      res->tm_year += 1;
	      res->tm_yday = 0;
	    }
	}
    }
  else if (res->tm_hour < 0)
    {
      res->tm_yday -= 1;
      res->tm_wday -= 1;
      if (res->tm_wday < 0)
	res->tm_wday = 6;
      res->tm_mday -= 1;
      res->tm_hour += 24;
      if (res->tm_mday == 0)
	{
	  res->tm_mon -= 1;
	  if (res->tm_mon < 0)
	    {
	      res->tm_mon = 11;
	      res->tm_year -= 1;
	      res->tm_yday = 364 + isleap(res->tm_year + YEAR_BASE);
	    }
	  res->tm_mday = ip[res->tm_mon];
	}
    }
  TZ_UNLOCK;

  return (res);
}
