/**
 * File:	modules/Timezone.ycp
 * Package:	Country settings
 * Summary:	Timezone related stuff
 * Authors:	Klaus Kaempf <kkaempf@suse.de>
 *		Thomas Roelz <tom@suse.de>
 *
 * $Id: Timezone.ycp 22162 2005-03-02 14:11:40Z jsuchome $
 */

{

module "Timezone";
textdomain "country";

import "Arch";
import "Language";
import "Misc";
import "Mode";
import "Stage";
import "String";
import "ProductFeatures";

// --------------------------------------------------------------
// START: Globally defined data to be accessed via Timezone::<variable>
// --------------------------------------------------------------

global string timezone = "";	// e.g. "Europe/Berlin"

// hwclock parameter
// possible values:
//	 ""		dont change timezone
//	 "-u"		system clock runs UTC
//   "--localtime"	system clock runs localtime
global string hwclock = "";

// The default timezone if set.
//
global string default_timezone = "";

// Flag indicating if the user has chosen a timezone.
// To be set from outside.
//
global boolean user_decision = false;
global boolean user_hwclock = false;

global integer diff = 0;


/**
 * If there is windows partition, assume that local time is used
 */
global boolean windows_partition	= false;

map push = $[];

// ------------------------------------------------------------------
// END: Globally defined data to be accessed via Timezone::<variable>
// ------------------------------------------------------------------



// ------------------------------------------------------------------
// START: Locally defined data
// ------------------------------------------------------------------

string name = "";

list < map < string, any> > zonemap = [];
map<string, string> lang2tz = $[];

// ------------------------------------------------------------------
// END: Locally defined data
// ------------------------------------------------------------------


// ---------------------------------------------------------------------------------------
// START: Globally defined functions
// ---------------------------------------------------------------------------------------

/*
 * get_lang2tz()
 *
 * Get the language --> timezone conversion map.
 *
 * @param	-
 *
 * @return  conversion map
 *
 * @see	get_zonemap()
 */

define map<string, string> get_lang2tz()
    ``{
    // language --> timezone database, e.g. "de_DE" : "Europe/Berlin"
    //
    if( size(lang2tz)==0 )
	{
	lang2tz = (map<string, string>)SCR::Read( .target.yast2, "lang2tz.ycp" );
	if( lang2tz == nil ) lang2tz = $[];
	}
    return( lang2tz );
    }	// get_lang2tz()

/*
 * get_zonemap()
 *
 * Get the timezone database.
 *
 * @param	-
 *
 * @return  timezone DB (map)
 *
 * @see	get_lang2tz()
 */

global define list < map < string, any> > get_zonemap() {

    if( size(zonemap)==0 )
	{
	zonemap = (list < map < string, any> >)eval(SCR::Read( .target.yast2, "timezone_raw.ycp" ));
	if( zonemap == nil ) zonemap = [];
	}
    return( zonemap );
} // get_zonemap()

// ------------------------------------------------------------------
// END: Locally defined functions
// ------------------------------------------------------------------
/**
 * Set()
 *
 * Set system to selected timezone.
 *
 * @param	string timezone to select, e.g. "Europe/Berlin"
 *
 * @return	the number of the region that contains the timezone
 *
 */
global define integer Set (string zone, boolean really) {

    list <map <string, any> > zmap = get_zonemap();

    // Set the new timezone internally
    timezone = zone;

    integer sel = 0;
    while (sel<size (zmap) && !haskey (zmap[sel,"entries"]:$[], zone))
    {
	sel = sel + 1;
    }

    name	= zmap [sel,"name"]:"" + " / " + zmap[sel,"entries",zone]:zone;

    // Adjust system to the new timezone.
    //
    if (!Mode::config () && really )
    {
	string cmd = "/usr/sbin/zic -l " + timezone;
	y2milestone( "Set cmd %1", cmd );
	y2milestone( "Set ret %1", SCR::Execute( .target.bash_output, cmd ));
	if (!Arch::s390 ())
	{
	    cmd = "/sbin/hwclock --hctosys " + hwclock;
	    y2milestone( "Set cmd %1", cmd );
	    y2milestone( "Set ret %1", SCR::Execute(.target.bash_output, cmd ));
	}
    }

    // On first assignment store default timezone.
    //
    if (default_timezone == "")	// not yet assigned
    {
	default_timezone = timezone;
	y2milestone( "Set default timezone: <%1>", timezone );
    }

    y2milestone( "Set timezone:%1 sel:%2 name:%3", timezone, sel, name );
    return sel;
}

/**
 * Convert the duplicated timezone to the only one supported
 * Temporary solution - a result of discussion of bug #47472
 * @param tmz current timezone
 */
global define string UpdateTimezone (string tmz) {

    string updated_tmz	= tmz;

    map tmz_duplicates	= $[
	"Australia/Adelaide"	: "Australia/South",
	"Australia/Darwin"	: "Australia/North",
	"Australia/Sydney"	: "Australia/NSW",
	"Australia/Canberra"	: "Australia/ACT",
	"Australia/Brisbane"	: "Australia/Queensland",
	"Australia/Hobart"	: "Australia/Tasmania",
	"Australia/Perth"	: "Australia/West"
	// etc.
    ];

    if (haskey (tmz_duplicates, tmz))
    {
	updated_tmz	= tmz_duplicates[tmz]:tmz;
	y2milestone ("updating timezone from %1 to %2", tmz, updated_tmz);
	// TODO if still not present, search file in /usr/share/zoneinfo
    }

    return updated_tmz;
}

/**
  * Timezone()
  *
  * The module constructor.
  * Sets the proprietary module data defined globally for public access.
  * This is done only once (and automatically) when the module is loaded for the first time.
  * Calls Set() in initial mode.
  * Reads current timezone from sysconfig in normal mode.
  *
  * @param	-
  *
  * @return  -
  *
  * @see	Set()
  */
global define void Timezone() {

    // Set default values.
    //
    hwclock = "-u";
    if (Stage::initial ())
    {
	// language --> timezone database, e.g. "de_DE" : "Europe/Berlin"
	map<string, string> lang2tz = get_lang2tz();

	string new_timezone = lang2tz[Language::language]:"";
	y2milestone( "Timezone new_timezone %1", new_timezone );

	if (new_timezone != "")
	{
	    Set (new_timezone, true);
	}
    }
    else if (!Mode::config ())
    {
	timezone = Misc::SysconfigRead(.sysconfig.clock.TIMEZONE, timezone );

	default_timezone =
	    Misc::SysconfigRead(.sysconfig.clock.DEFAULT_TIMEZONE,
	                        default_timezone);
	hwclock = Misc::SysconfigRead(.sysconfig.clock.HWCLOCK, hwclock);
    }
    return;
}


/**
 * Set the new time and date given by user
 */
global define void SetTime (string year, string month, string day,
                            string hour, string minute, string second ) {
    if (!Arch::s390 ())
    {
	string date = sformat( " --date=\"%1/%2/%3 %4:%5:%6\" ", month, day,
	                       year, hour, minute, second );
	string cmd = "";
	if (size (timezone) >0 && hwclock != "--localtime")
	{
	    cmd = "TZ=" + timezone + " ";
	}
	cmd = cmd + "/sbin/hwclock --set " + hwclock + date;
	y2milestone( "SetTime cmd %1", cmd );
	SCR::Execute(.target.bash, cmd );
	cmd = "/sbin/hwclock --hctosys " + hwclock;
	y2milestone( "SetTime cmd %1", cmd );
	SCR::Execute(.target.bash, cmd );
    }
};

global define void PushVal()
    ``{
    push = $[ "hwclock" : hwclock, "timezone" : timezone ];
    y2milestone( "PushVal map %1", push );
    }

global define void PopVal()
    ``{
    y2milestone( "PopVal timezone %1 hwclock %2", timezone, hwclock );
    if( haskey( push, "hwclock" ))
	hwclock = push["hwclock"]:hwclock;
    if( haskey( push, "timezone" ))
	timezone = push["timezone"]:timezone;
    push = $[];
    y2milestone( "PopVal timezone %1 hwclock %2", timezone, hwclock );
    }

/**
 * GetTimezoneForLanguage()
 *
 * Get the timezone for the given system language.
 *
 * @param	System language code, e.g. "en_US".
 *		Default timezone to be returned if nothing found.
 *
 * @return  The timezone for this language, e.g. "english-us"
 *		or the default value if nothing found.
 *
 * @see	-
 */
global define string GetTimezoneForLanguage (string sys_language,
                                             string default_timezone)
{
    // The system_language --> timezone conversion map.
    //
    map<string, string> lang2timezone = get_lang2tz();
    string ret = lang2timezone[sys_language]:default_timezone;

    y2milestone ("language %1 default timezone %2 returned timezone %3",
                 sys_language, default_timezone, ret);
    return ret;
}

/**
 * Set the timezone for the given system language.
 * @param	System language code, e.g. "en_US".
 * @return the number of the region that contains the timezone
 */
global define void SetTimezoneForLanguage (string sys_language) {

    string tmz = GetTimezoneForLanguage (sys_language, "US/Mountain");
    y2debug ("language %1 proposed timezone %2", sys_language, tmz);
    if (tmz != "")
    {
	Set (tmz, true);
    }
}

/**
 * Return translated country name of given timezone
 * @param timezone value (as saved in sysconfig/clock)
 */
global define string GetTimezoneCountry (string zone) {

    list zmap = (list) eval (SCR::Read (.target.yast2, "timezone_raw.ycp"));

    integer sel = 0;
    while (sel<size (zmap) && !haskey (zmap[sel,"entries"]:$[], zone))
    {
	sel = sel + 1;
    }
    return zmap [sel,"name"]:"" + " / " + zmap[sel,"entries",zone]:zone;
}

/**
 * GetDateTime()
 *
 * Get the output of date "+%H:%M:%S - %Y-%m-%d" or in locale defined format
 *
 * @param	flag if to get real system time or if to simulate changed
 *		timezone settings with TZ=
 * @param	if the date and time should be returned in locale defined format
 *
 * @return  The string output.
 *
 * @see	-
 */
global define string GetDateTime (boolean real_time, boolean locale_format) {

    string cmd = "";

    string date_format = (locale_format && Mode::normal ())
	? "+%c" : "+%H:%M:%S - %d-%m-%Y";

    y2milestone( "GetDateTime hwclock %1 real:%2", hwclock, real_time );
    if (!real_time)
    {
	integer ds = 0;
	if (diff!=0)
	{
	    integer t = 0;
	    map out	= (map) SCR::Execute (.target.bash_output, "date +%z");
	    string tzd = out["stdout"]:"";
	    y2milestone( "GetDateTime tcd=%1", tzd );
	    t = tointeger( String::CutZeros( substring( tzd, 1, 2 )));
	    ds = ds + t * 3600;
	    t = tointeger( String::CutZeros( substring( tzd, 3, 2 )));
	    ds = ds + t * 60;
	    if (substring (tzd, 0, 1) == "-")
	    {
		ds = -ds;
	    }
	    y2milestone( "GetDateTime ds %1 diff %2", ds, diff );
	}
	cmd = "";
	if (hwclock != "--localtime")
	{
	    cmd = sformat( "TZ=%1 ", timezone );
	}
	cmd = cmd + sformat( "/bin/date \"%1\" \"--date=now %2sec\"",
			     date_format, ds*diff );
    }
    else
	{
	cmd = sformat( "/bin/date \"%1\"", date_format );
	}
    y2milestone( "GetDateTime cmd=%1", cmd );
    map out	= (map) SCR::Execute (.target.bash_output, cmd);
    string local_date = out["stdout"]:"";

    y2milestone( "GetDateTime local_date=%1", local_date );

    return( local_date );
    }

global define void  ResetZonemap()
    ``{
    zonemap = [];
    }

/**
 * MakeProposal()
 *
 * Return proposal string and set system timezone.
 *
 * @param	boolean force_reset
 *		boolean language_changed
 *
 * @return	string	user readable description.
 *		If force_reset is true reset the module to the timezone
 *		stored in default_timezone.
 */

global define string MakeProposal (boolean force_reset,
                                   boolean language_changed )
{
    y2milestone ("force_reset: %1", force_reset);
    y2milestone ("language_changed: %1 user_decision %2 user_hwclock %3",
	language_changed, user_decision, user_hwclock);

    if (language_changed)
	ResetZonemap();

    if (!user_hwclock || force_reset)
    {
	hwclock = "-u";
	if (windows_partition || Arch::board_mac ())
	{
	    // Win partitions present ==> assume local time.
	    //
	    hwclock = "--localtime";
	}
    }
    if (force_reset)
    {
	// If user wants to reset do it if a default is available.
	//
	if( default_timezone != "" )
	    {
	    Set( default_timezone, true );	// reset
	    }

	// Reset user_decision flag.
	//
	user_decision = false;
    }
    else	// no reset
    {
	// Only follow the language if the user has never actively chosen
	// a timezone. The indicator for this is user_decision which is
	// set from outside the module.
	//
	if( user_decision || Mode::autoinst () || ProductFeatures::timezone!="")
	    {
	    if( language_changed )
		{
		y2milestone("User has chosen a timezone; not following language - only retranslation.");

		Set( timezone, true );
		}
	    }
	else
	    {
	    // User has not yet chosen a timezone ==> follow language.
	    //
	    string local_timezone =
		GetTimezoneForLanguage( Language::language, "US/Mountain");

	    if( local_timezone != "" )
		{
		Set( local_timezone, true );
		default_timezone = local_timezone;
		}
	    else
		{
		if( language_changed )
		    {
		    y2error("Can't follow language - only retranslation");

		    Set( timezone, true );
		    }
		}
	    }
    }

    // label text (Clock setting)
    string clock_setting = _("UTC");

    if (hwclock == "--localtime")
	// label text, Clock setting: local time (not UTC)
	clock_setting = _("local time");

    // label text
    clock_setting = _("Hardware clock set to") + " " + clock_setting;

    string date = GetDateTime (true, true);

    y2milestone( "MakeProposal hwclock %1", hwclock );

    return name + " - " + clock_setting + " " + date;
}

/**
  * Selection()
  *
  * Return a map of ids and names to build up a selection list
  * for the user. The key is used later in the Set function
  * to select this timezone. The name is a translated string.
  *
  * @param	-
  *
  * @return	map	map for timezones
  *			'timezone_id' is used internally in Set and Probe
  *			functions. 'timezone_name' is a user-readable string.
  *			Uses Language::language for translation.
  * @see Set()
  */

global define list Selection (integer num) {

    list<map<string,any> > zmap = get_zonemap();

    list<list<string> > trl	= maplist (
	string key, string name, zmap[num,"entries"]:$[], ``([ name, key ]));

    trl	= sort (list<string> a, list<string> b, trl, {
	list lsorted	= lsort ([a[0]:"", b[0]:""]);
	return (lsorted[0]:"" == a[0]:"" && a[0]:"" != b[0]:"");
    });
    y2debug ("trl = %1", trl);

    return maplist (list e, trl, ``(`item (`id(e[1]:""), e[0]:"", false)));
}

/**
 * Return list of regions for timezone selection list
 */
global define list Region() {

    integer num = -1;
    return maplist (map entry, get_zonemap (), {
	num = num + 1;
	return (`item (`id(num), entry["name"]:"", false ));
    });
}


/**
 * Save()
 *
 * Save timezone to target sysconfig.
 *
 * @param	-
 *
 * @return	-
 */
global define void Save() {

    if ( Mode::update () )
    {
	return;
    }

    SCR::Write(.sysconfig.clock.TIMEZONE, timezone);
    SCR::Write(.sysconfig.clock.DEFAULT_TIMEZONE, default_timezone);
    SCR::Write(.sysconfig.clock.HWCLOCK, hwclock);

    SCR::Write(.sysconfig.clock, nil);	// flush

    y2milestone( "Save Saved data for timezone: <%1>", timezone );

    return;
}


/**
 * Return current date and time in the map
 */
global define map GetDateTimeMap() {

    map ret = $[];
    list dparts = filter (
	string v,splitstring (GetDateTime (false,false), " -:" ),``(size(v)>0));

    ret["hour"]		= dparts[0]:"";
    ret["minute"]	= dparts[1]:"";
    ret["second"]	= dparts[2]:"";
    ret["day"]		= dparts[3]:"";
    ret["month"]	= dparts[4]:"";
    ret["year"]		= deletechars (dparts[5]:"", "\n");
    y2milestone( "GetDateTimeMap dparts %1 ret %2", dparts, ret );
    return( ret );
}

global define boolean CheckTime( string hour, string minute, string second )
    ``{
    boolean ret = true;
    integer tmp = tointeger( String::CutZeros(hour) );
    ret = ret && tmp>=0 && tmp<24;
    tmp = tointeger( String::CutZeros(minute) );
    ret = ret && tmp>=0 && tmp<60;
    tmp = tointeger( String::CutZeros(second) );
    ret = ret && tmp>=0 && tmp<60;
    return( ret );
    }

global define boolean CheckDate( string day, string month, string year )
    ``{
    list mdays = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
    boolean ret = true;
    integer yea = tointeger( String::CutZeros(year) );
    integer mon = tointeger( String::CutZeros(month) );
    ret = ret && mon>=1 && mon<=12;
    if( yea%4==0 && (yea%100!=0 || yea%400==0))
	{
	mdays[1] = 29;
	}
    integer da = tointeger(  String::CutZeros(day) );
    ret = ret && da>=1 && da<=mdays[mon-1]:0;
    ret = ret && yea>=1970 && yea<2032;
    return( ret );
    }

// does the hwclock run on UTC only ? -> skip asking
global define boolean utc_only () {

    y2milestone( "Arch::sparc () %1 Arch::board_iseries () %2 Arch::board_chrp () %3 Arch::board_prep () %4", Arch::sparc (), Arch::board_iseries (), Arch::board_chrp (), Arch::board_prep () );

    return (Arch::sparc () || Arch::board_iseries () ||
	    Arch::board_chrp () || Arch::board_prep ());
}

// ------------------------------------------------------------------
// END: Globally defined functions
// ------------------------------------------------------------------



}

// -EOF-
