/*
Copyright (c) 1994 Cygnus Support.
All rights reserved.

Redistribution and use in source and binary forms are permitted
provided that the above copyright notice and this paragraph are
duplicated in all such forms and that any documentation,
and/or other materials related to such
distribution and use acknowledge that the software was developed
at Cygnus Support, Inc.  Cygnus Support, Inc. may not be used to
endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */
/*
 * 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;
    const uint8_t *ip;

    res = gmtime_r(tim_p, res);
    if (res == NULL)
        return NULL;

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

    tzset();

    __LIBC_LOCK();

    if (daylight) {
        if (year == __tzinfo.year || __tzcalc_limits(year))
            res->tm_isdst
                = (__tzinfo.north
                       ? (*tim_p >= __tzinfo.rule[0].change && *tim_p < __tzinfo.rule[1].change)
                       : (*tim_p >= __tzinfo.rule[0].change || *tim_p < __tzinfo.rule[1].change));
        else
            res->tm_isdst = -1;
    } else
        res->tm_isdst = 0;

    offset = (res->tm_isdst == 1 ? __tzinfo.rule[1].offset : __tzinfo.rule[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];
        }
    }
    __LIBC_UNLOCK();

    return (res);
}
