• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

yast / yast-country / 15022004526

14 May 2025 01:29PM UTC coverage: 42.791% (-0.2%) from 43.025%
15022004526

Pull #328

github

web-flow
Merge f0e471a76 into 17e9fd3be
Pull Request #328: Load required "setxkbmap" package on demand (bsc#1243088)

1484 of 3468 relevant lines covered (42.79%)

12.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.98
/timezone/src/modules/Timezone.rb
1
# encoding: utf-8
2

3
# ------------------------------------------------------------------------------
4
# Copyright (c) 2012 Novell, Inc. All Rights Reserved.
5
#
6
#
7
# This program is free software; you can redistribute it and/or modify it under
8
# the terms of version 2 of the GNU General Public License as published by the
9
# Free Software Foundation.
10
#
11
# This program is distributed in the hope that it will be useful, but WITHOUT
12
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License along with
16
# this program; if not, contact Novell, Inc.
17
#
18
# To contact Novell about this file by physical or electronic mail, you may find
19
# current contact information at www.novell.com.
20
# ------------------------------------------------------------------------------
21
# File:        modules/Timezone.ycp
22
# Package:        Country settings
23
# Summary:        Timezone related stuff
24
# Authors:        Klaus Kaempf <kkaempf@suse.de>
25
#                Thomas Roelz <tom@suse.de>
26

27
require "yast"
1✔
28

29
begin
30
  require "y2storage"
1✔
31
rescue LoadError
32
  # Ignore y2storage not being available (bsc#1058869)
33
end
34

35
module Yast
1✔
36
  class TimezoneClass < Module
1✔
37
    include Yast::Logger
1✔
38

39
    def main
1✔
40
      textdomain "country"
62✔
41

42
      Yast.import "Arch"
62✔
43
      Yast.import "FileUtils"
62✔
44
      Yast.import "Language"
62✔
45
      Yast.import "Misc"
62✔
46
      Yast.import "Mode"
62✔
47
      Yast.import "Stage"
62✔
48
      Yast.import "String"
62✔
49
      Yast.import "ProductFeatures"
62✔
50

51
      # --------------------------------------------------------------
52
      # START: Globally defined data to be accessed via Timezone::<variable>
53
      # --------------------------------------------------------------
54

55
      @timezone = "" # e.g. "Europe/Berlin"
62✔
56

57
      # hwclock parameter
58
      # possible values:
59
      #         ""                dont change timezone
60
      #         "-u"                system clock runs UTC
61
      #   "--localtime"        system clock runs localtime
62
      @hwclock = ""
62✔
63

64
      # The default timezone if set.
65
      #
66
      @default_timezone = ""
62✔
67

68
      # Flag indicating if the user has chosen a timezone.
69
      # To be set from outside.
70
      #
71
      @user_decision = false
62✔
72
      @user_hwclock = false
62✔
73

74
      # If NTP is configured
75
      @ntp_used = false
62✔
76

77
      @diff = 0
62✔
78

79
      # if anything was modified (currently for auto client only)
80
      @modified = false
62✔
81

82
      # If there is windows partition, assume that local time is used
83
      @windows_partition = false
62✔
84

85
      # if mkinitrd should be called at the end
86
      @call_mkinitrd = false
62✔
87

88
      # translation of correct timezone to the one that could be shown in map widget
89
      @yast2zonetab = {
90
        "Mideast/Riyadh87" => "Asia/Riyadh",
62✔
91
        "Mideast/Riyadh88" => "Asia/Riyadh",
92
        "Mideast/Riyadh89" => "Asia/Riyadh",
93
        "Europe/Vatican"   => "Europe/Rome"
94
      }
95

96
      # on init, translate these to correct ones
97
      @obsoleted_zones = {
98
        "Iceland"                  => "Atlantic/Reykjavik",
62✔
99
        "Europe/Belfast"           => "Europe/London",
100
        "Australia/South"          => "Australia/Adelaide",
101
        "Australia/North"          => "Australia/Darwin",
102
        "Australia/NSW"            => "Australia/Sydney",
103
        "Australia/ACT"            => "Australia/Canberra",
104
        "Australia/Queensland"     => "Australia/Brisbane",
105
        "Australia/Tasmania"       => "Australia/Hobart",
106
        "Australia/Victoria"       => "Australia/Melbourne",
107
        "Australia/West"           => "Australia/Perth",
108
        "US/Alaska"                => "America/Anchorage",
109
        "US/Aleutian"              => "America/Adak",
110
        "US/Arizona"               => "America/Phoenix",
111
        "US/Central"               => "America/Chicago",
112
        "US/East-Indiana"          => "America/Indiana/Indianapolis",
113
        "US/Hawaii"                => "Pacific/Honolulu",
114
        "US/Indiana-Starke"        => "America/Indiana/Knox",
115
        "US/Michigan"              => "America/Detroit",
116
        "US/Mountain"              => "America/Denver",
117
        "US/Pacific"               => "America/Los_Angeles",
118
        "US/Samoa"                 => "Pacific/Pago_Pago",
119
        "US/Eastern"               => "America/New_York",
120
        "Canada/Atlantic"          => "America/Halifax",
121
        "Canada/Central"           => "America/Winnipeg",
122
        "Canada/Eastern"           => "America/Toronto",
123
        "Canada/Saskatchewan"      => "America/Regina",
124
        "Canada/East-Saskatchewan" => "America/Regina",
125
        "Canada/Mountain"          => "America/Edmonton",
126
        "Canada/Newfoundland"      => "America/St_Johns",
127
        "Canada/Pacific"           => "America/Vancouver",
128
        "Canada/Yukon"             => "America/Whitehorse",
129
        "America/Buenos_Aires"     => "America/Argentina/Buenos_Aires",
130
        "America/Virgin"           => "America/St_Thomas",
131
        "Brazil/Acre"              => "America/Rio_Branco",
132
        "Brazil/East"              => "America/Sao_Paulo",
133
        "Brazil/West"              => "America/Manaus",
134
        "Chile/Continental"        => "America/Santiago",
135
        "Chile/EasterIsland"       => "Pacific/Easter",
136
        "Mexico/BajaNorte"         => "America/Tijuana",
137
        "Mexico/BajaSur"           => "America/Mazatlan",
138
        "Mexico/General"           => "America/Mexico_City",
139
        "Jamaica"                  => "America/Jamaica",
140
        "Asia/Beijing"             => "Asia/Shanghai",
141
        "Asia/Harbin"              => "Asia/Shanghai",
142
        "Asia/Macao"               => "Asia/Macau",
143
        "Israel"                   => "Asia/Jerusalem",
144
        "Asia/Tel_Aviv"            => "Asia/Jerusalem",
145
        "Hongkong"                 => "Asia/Hong_Kong",
146
        "Japan"                    => "Asia/Tokyo",
147
        "ROK"                      => "Asia/Seoul",
148
        "Africa/Timbuktu"          => "Africa/Bamako",
149
        "Egypt"                    => "Africa/Cairo"
150
      }
151
      # ------------------------------------------------------------------
152
      # END: Globally defined data to be accessed via Timezone::<variable>
153
      # ------------------------------------------------------------------
154

155

156

157
      # ------------------------------------------------------------------
158
      # START: Locally defined data
159
      # ------------------------------------------------------------------
160

161
      # internal map used to store initial data
162
      @push = {}
62✔
163

164

165
      @name = ""
62✔
166

167
      # list with maps, each map provides time zone information about one region
168
      @zonemap = []
62✔
169

170
      # 'language --> default timezone' conversion map
171
      @lang2tz = {}
62✔
172

173
      # remember if /sbin/hwclock --hctosys was called, it can be done only once (bnc#584484)
174
      @systz_called = false
62✔
175

176
      # timezone is read-only
177
      @readonly = nil
62✔
178

179
      Timezone()
62✔
180
    end
181

182
    # ------------------------------------------------------------------
183
    # END: Locally defined data
184
    # ------------------------------------------------------------------
185

186

187
    # -----------------------------------------------------------------------------
188
    # START: Globally defined functions
189
    # -----------------------------------------------------------------------------
190

191
    # get_lang2tz()
192
    #
193
    # Get the language --> timezone conversion map.
194
    #
195
    # @return  conversion map
196
    #
197
    # @see #get_zonemap()
198

199
    def get_lang2tz
1✔
200
      if Builtins.size(@lang2tz) == 0
10✔
201
        base_lang2tz = Convert.to_map(
8✔
202
          SCR.Read(path(".target.yast2"), "lang2tz.ycp")
203
        )
204
        base_lang2tz = {} if base_lang2tz == nil
8✔
205

206
        @lang2tz = Convert.convert(
8✔
207
          Builtins.union(base_lang2tz, Language.GetLang2TimezoneMap(true)),
208
          :from => "map",
209
          :to   => "map <string, string>"
210
        )
211
      end
212
      deep_copy(@lang2tz)
10✔
213
    end
214

215
    # get_zonemap()
216
    #
217
    # Get the timezone database.
218
    #
219
    # @return  timezone DB (map)
220
    #
221
    # @see #get_lang2tz()
222

223
    def get_zonemap
1✔
224
      if Builtins.size(@zonemap) == 0
24✔
225
        zmap = Convert.convert(
20✔
226
          Builtins.eval(WFM.Read(path(".local.yast2"), "timezone_raw.ycp")),
227
          :from => "any",
228
          :to   => "list <map <string, any>>"
229
        )
230
        zmap = [] if zmap == nil
20✔
231

232
        @zonemap = Builtins.sort(zmap) do |a, b|
20✔
233
          # [ "USA", "Canada" ] -> [ "Canada", "USA" ]
234
          # bnc#385172: must use < instead of <=, the following means:
235
          # strcoll(x) <= strcoll(y) && strcoll(x) != strcoll(y)
236
          lsorted = Builtins.lsort(
940✔
237
            [Ops.get_string(a, "name", ""), Ops.get_string(b, "name", "")]
238
          )
239
          lsorted_r = Builtins.lsort(
940✔
240
            [Ops.get_string(b, "name", ""), Ops.get_string(a, "name", "")]
241
          )
242
          Ops.get_string(lsorted, 0, "") == Ops.get_string(a, "name", "") &&
940✔
243
            lsorted == lsorted_r
244
        end
245
      end
246
      deep_copy(@zonemap)
24✔
247
    end
248

249
    # ------------------------------------------------------------------
250
    # END: Locally defined functions
251
    # ------------------------------------------------------------------
252
    # Set()
253
    #
254
    # Set system to selected timezone.
255
    #
256
    # @param        string timezone to select, e.g. "Europe/Berlin"
257
    #
258
    # @return        the number of the region that contains the timezone
259
    #
260
    def Set(zone, really)
1✔
261
      # Do not update the timezone if it's forced and it was already set
262
      if (Mode.installation || Mode.update) && readonly && !@timezone.empty?
22✔
263
        log.info "Timezone is read-only and cannot be changed during installation"
2✔
264
      else
265
        # Set the new timezone internally
266
        @timezone = zone
20✔
267
      end
268

269
      zmap = get_zonemap
22✔
270

271
      sel = 0
22✔
272
      while Ops.less_than(sel, Builtins.size(zmap)) &&
22✔
273
          !Builtins.haskey(Ops.get_map(zmap, [sel, "entries"], {}), @timezone)
274
        sel = Ops.add(sel, 1)
250✔
275
      end
276

277
      @name = Ops.add(
22✔
278
        Ops.add(Ops.get_string(zmap, [sel, "name"], ""), " / "),
279
        Ops.get_string(zmap, [sel, "entries", @timezone], @timezone)
280
      )
281

282
      # Adjust system to the new timezone.
283
      #
284
      if !Mode.config && really
22✔
285
        textmode = Language.GetTextMode
16✔
286
        # turn off the screensaver when clock can change radically (bnc#455771)
287
        # (in non-firstboot cases, installation process handles it)
288
        if Stage.firstboot && !textmode
16✔
289
          SCR.Execute(path(".target.bash"), "/usr/bin/xset -dpms")
×
290
          SCR.Execute(path(".target.bash"), "/usr/bin/xset s reset")
×
291
          SCR.Execute(path(".target.bash"), "/usr/bin/xset s off")
×
292
        end
293
        cmd = Ops.add("/usr/sbin/zic -l ", @timezone)
16✔
294
        Builtins.y2milestone("Set cmd %1", cmd)
16✔
295
        Builtins.y2milestone(
16✔
296
          "Set ret %1",
297
          SCR.Execute(path(".target.bash_output"), cmd)
298
        )
299
        unless Stage.initial
16✔
300
          cmd = "/usr/bin/systemctl try-restart systemd-timedated.service"
10✔
301
          Builtins.y2milestone(
10✔
302
            "restarting timedated service: %1",
303
            SCR.Execute(path(".target.bash_output"), cmd)
304
          )
305
        end
306
        if !Arch.s390
16✔
307
          cmd = Ops.add("/sbin/hwclock --hctosys ", @hwclock)
16✔
308
          if Stage.initial && @hwclock == "--localtime"
16✔
309
            if !@systz_called
×
310
              cmd = "/sbin/hwclock --systz --localtime --noadjfile && touch /dev/shm/warpclock"
×
311
              @systz_called = true
×
312
            end
313
          end
314
          Builtins.y2milestone("Set cmd %1", cmd)
16✔
315
          Builtins.y2milestone(
16✔
316
            "Set ret %1",
317
            SCR.Execute(path(".target.bash_output"), cmd)
318
          )
319
        end
320
        if Stage.firstboot && !textmode
16✔
321
          SCR.Execute(path(".target.bash"), "/usr/bin/xset s on")
×
322
          SCR.Execute(path(".target.bash"), "/usr/bin/xset +dpms")
×
323
        end
324
      end
325

326
      # On first assignment store default timezone.
327
      #
328
      if @default_timezone == ""
22✔
329
        @default_timezone = @timezone
17✔
330
        Builtins.y2milestone("Set default timezone: <%1>", @timezone)
17✔
331
      end
332

333
      Builtins.y2milestone(
22✔
334
        "Set timezone:%1 sel:%2 name:%3",
335
        @timezone,
336
        sel,
337
        @name
338
      )
339
      sel
22✔
340
    end
341

342
    # Convert the duplicated timezone to the only one supported
343
    # Temporary solution - a result of discussion of bug #47472
344
    # @param [String] tmz current timezone
345
    def UpdateTimezone(tmz)
1✔
346
      updated_tmz = tmz
10✔
347

348
      if Builtins.haskey(@obsoleted_zones, tmz)
10✔
349
        updated_tmz = Ops.get(@obsoleted_zones, tmz, tmz)
5✔
350
        Builtins.y2milestone(
5✔
351
          "updating timezone from %1 to %2",
352
          tmz,
353
          updated_tmz
354
        )
355
      end
356

357
      updated_tmz
10✔
358
    end
359

360
    # Read the content of /etc/adjtime
361
    def ReadAdjTime
1✔
362
      cont = Convert.convert(
59✔
363
        SCR.Read(path(".etc.adjtime")),
364
        :from => "any",
365
        :to   => "list <string>"
366
      )
367
      if cont == nil
59✔
368
        Builtins.y2warning("/etc/adjtime not available or not readable")
2✔
369
      end
370
      if Builtins.size(cont) != 3
59✔
371
        Builtins.y2warning("/etc/adjtime has wrong number of lines: %1", cont)
59✔
372
        cont = nil
59✔
373
      end
374
      deep_copy(cont)
59✔
375
    end
376

377

378
    # Read timezone settings from sysconfig
379
    def Read
1✔
380
      @default_timezone = Misc.SysconfigRead(
61✔
381
        path(".sysconfig.clock.DEFAULT_TIMEZONE"),
382
        @default_timezone
383
      )
384
      @timezone = @default_timezone
61✔
385

386
      # /etc/localtime has priority over sysconfig value of timezone
387
      if FileUtils.IsLink("/etc/localtime")
61✔
388
        tz_file = Convert.to_string(
58✔
389
          SCR.Read(path(".target.symlink"), "/etc/localtime")
390
        )
391
        if tz_file != nil &&
58✔
392
            Builtins.substring(tz_file, 0, 20) == "/usr/share/zoneinfo/"
393
          @timezone = Builtins.substring(tz_file, 20)
1✔
394
          Builtins.y2milestone(
1✔
395
            "time zone read from /etc/localtime: %1",
396
            @timezone
397
          )
398
        end
399
      end
400

401
      adjtime = ReadAdjTime()
61✔
402
      if Builtins.size(adjtime) == 3
61✔
403
        if Ops.get(adjtime, 2, "") == "LOCAL"
1✔
404
          @hwclock = "--localtime"
1✔
405
        elsif Ops.get(adjtime, 2, "") == "UTC"
×
406
          @hwclock = "-u"
×
407
        end
408
        Builtins.y2milestone("content of /etc/adjtime: %1", adjtime)
1✔
409
      else
410
        # use sysconfig value as a backup (if available)
411
        @hwclock = Misc.SysconfigRead(
60✔
412
          path(".sysconfig.clock.HWCLOCK"),
413
          @hwclock
414
        )
415
      end
416

417
      # get name for cloning purposes
418
      if Mode.config
61✔
419
        zmap = get_zonemap
×
420
        sel = 0
×
421
        while Ops.less_than(sel, Builtins.size(zmap)) &&
×
422
            !Builtins.haskey(Ops.get_map(zmap, [sel, "entries"], {}), @timezone)
423
          sel = Ops.add(sel, 1)
×
424
        end
425
        @name = Ops.add(
×
426
          Ops.add(Ops.get_string(zmap, [sel, "name"], ""), " / "),
427
          Ops.get_string(zmap, [sel, "entries", @timezone], @timezone)
428
        )
429
      end
430

431
      nil
61✔
432
    end
433

434
    # Timezone()
435
    #
436
    # The module constructor.
437
    # Sets the proprietary module data defined globally for public access.
438
    # This is done only once (and automatically) when the module is loaded for the first time.
439
    # Calls Set() in initial mode.
440
    # Reads current timezone from sysconfig in normal mode.
441
    #
442
    # @see #Set()
443
    def Timezone
1✔
444
      # Set default values.
445
      #
446
      @hwclock = "-u"
62✔
447
      if Stage.initial && !Mode.live_installation
62✔
448
        # language --> timezone database, e.g. "de_DE" : "Europe/Berlin"
449
        new_timezone =
450
          if readonly
5✔
451
            product_default_timezone
4✔
452
          else
453
            lang2tz = get_lang2tz
1✔
454
            Ops.get(lang2tz, Language.language, "")
1✔
455
          end
456

457
        Builtins.y2milestone("Timezone new_timezone %1", new_timezone)
5✔
458

459
        Set(new_timezone, true) if new_timezone != ""
5✔
460
      elsif !Mode.config
57✔
461
        Read()
57✔
462
      end
463
      nil
62✔
464
    end
465

466
    def CallMkinitrd
1✔
467
      Builtins.y2milestone("calling mkinitrd...")
×
468
      SCR.Execute(
×
469
        path(".target.bash"),
470
        # force and regenerate all is needed to ensure that change is applied to all kernel versions
471
        "/usr/bin/dracut --force --regenerate-all >> /var/log/YaST2/y2logmkinitrd 2>> /var/log/YaST2/y2logmkinitrd"
472
      )
473
      Builtins.y2milestone("... done")
×
474
      true
×
475
    end
476

477

478
    # Set the new time and date given by user
479
    def SetTime(year, month, day, hour, minute, second)
1✔
480
      return nil if Arch.s390
×
481

482
      timedate = "#{month}/#{day}/#{year} #{hour}:#{minute}:#{second}"
×
483

484
      if set_hwclock(timedate)
×
485
        sync_hwclock_to_system_time
×
486
      else
487
        # No hardware clock available (bsc#1103744)
488
        log.info("Fallback: Leaving HW clock untouched, setting only system time")
×
489
        set_system_time(timedate)
×
490
      end
491
      nil
×
492
    end
493

494
    # Set the Hardware Clock to the current System Time.
495
    def SystemTime2HWClock
1✔
496
      return nil if Arch.s390
×
497

498
      cmd = tz_prefix + "/sbin/hwclock --systohc #{@hwclock}"
×
499
      log.info("cmd #{cmd}")
×
500
      SCR.Execute(path(".target.bash"), cmd)
×
501
      nil
×
502
    end
503

504
    # Set the hardware clock with the given date.
505
    # @param timedate [String]
506
    # @return [Bool] true if success, false if error
507
    #
508
    def set_hwclock(date)
1✔
509
      cmd = tz_prefix + "/sbin/hwclock --set #{@hwclock} --date=\"#{date}\""
×
510
      log.info("set_hwclock: #{cmd}")
×
511
      SCR.Execute(path(".target.bash"), cmd) == 0
×
512
    end
513

514
    # Synchronize the hardware clock to the system time
515
    #
516
    def sync_hwclock_to_system_time
1✔
517
      cmd = "/sbin/hwclock --hctosys #{@hwclock}"
×
518
      log.info("sync_hwclock_to_system_time: #{cmd}")
×
519
      SCR.Execute(path(".target.bash"), cmd)
×
520
      @systz_called = true
×
521
    end
522

523
    # Set only the system time (leaving the hardware clock untouched)
524
    # @param timedate [String]
525
    #
526
    def set_system_time(timedate)
1✔
527
      cmd = tz_prefix + "/usr/bin/date --set=\"#{timedate}\""
×
528
      log.info("set_system_time: #{cmd}")
×
529
      SCR.Execute(path(".target.bash"), cmd)
×
530
    end
531

532
    # Return a "TZ=... " prefix for commands such as "hwclock" or "date" to set
533
    # the time zone environment variable temporarily for the duration of one
534
    # command.
535
    #
536
    # If nonempty, this will append a blank as a separator.
537
    #
538
    # @return [String]
539
    #
540
    def tz_prefix
1✔
541
      return "" if @hwclock == "--localtime"
3✔
542
      return "" if @timezone.empty?
3✔
543
      "TZ=#{@timezone} "
×
544
    end
545

546

547
    # GetTimezoneForLanguage()
548
    #
549
    # Get the timezone for the given system language.
550
    #
551
    # @param        System language code, e.g. "en_US".
552
    #                Default timezone to be returned if nothing found.
553
    #
554
    # @return  The timezone for this language, e.g. "US/Eastern"
555
    #                or the default value if nothing found.
556
    #
557
    # @see #-
558
    def GetTimezoneForLanguage(sys_language, default_timezone)
1✔
559
      # The system_language --> timezone conversion map.
560
      #
561
      lang2timezone = get_lang2tz
9✔
562
      ret = Ops.get(lang2timezone, sys_language, default_timezone)
9✔
563

564
      Builtins.y2milestone(
9✔
565
        "language %1 default timezone %2 returned timezone %3",
566
        sys_language,
567
        default_timezone,
568
        ret
569
      )
570
      ret
9✔
571
    end
572

573
    # Set the timezone for the given system language.
574
    # @param        System language code, e.g. "en_US".
575
    # @return the number of the region that contains the timezone
576
    def SetTimezoneForLanguage(sys_language)
1✔
577
      tmz = GetTimezoneForLanguage(sys_language, "US/Eastern")
×
578
      Builtins.y2debug("language %1 proposed timezone %2", sys_language, tmz)
×
579
      Set(tmz, true) if tmz != ""
×
580

581
      nil
×
582
    end
583

584
    # Return the language code for given timezone (by reverse searching the
585
    # "language -> timezone" map)
586
    # @param timezone, if empty the current one is used
587
    def GetLanguageForTimezone(tz)
1✔
588
      tz = @timezone if tz == "" || tz == nil
×
589

590
      lang = ""
×
591
      Builtins.foreach(get_lang2tz) do |code, tmz|
×
592
        if tmz == tz && (lang == "" || !Builtins.issubstring(lang, "_"))
×
593
          lang = code
×
594
        end
595
      end
596
      lang
×
597
    end
598

599
    # Return the country part of language code for given timezone
600
    # @param timezone, if empty the current one is used
601
    def GetCountryForTimezone(tz)
1✔
602
      Language.GetGivenLanguageCountry(GetLanguageForTimezone(tz))
×
603
    end
604

605
    # Return translated country name of given timezone
606
    # @param timezone value (as saved in sysconfig/clock)
607
    def GetTimezoneCountry(zone)
1✔
608
      zmap = Convert.to_list(
×
609
        Builtins.eval(SCR.Read(path(".target.yast2"), "timezone_raw.ycp"))
610
      )
611

612
      sel = 0
×
613
      while Ops.less_than(sel, Builtins.size(zmap)) &&
×
614
          !Builtins.haskey(Ops.get_map(zmap, [sel, "entries"], {}), zone)
615
        sel = Ops.add(sel, 1)
×
616
      end
617
      Ops.add(
×
618
        Ops.add(Ops.get_string(zmap, [sel, "name"], ""), " / "),
619
        Ops.get_string(zmap, [sel, "entries", zone], zone)
620
      )
621
    end
622

623
    # GetDateTime()
624
    #
625
    # Get the output of date "+%H:%M:%S - %Y-%m-%d" or in locale defined format
626
    #
627
    # @param        flag if to get real system time or if to simulate changed
628
    #                timezone settings with TZ=
629
    # @param        if the date and time should be returned in locale defined format
630
    #
631
    # @return  The string output.
632
    #
633
    def GetDateTime(real_time, locale_format)
1✔
634
      cmd = ""
16✔
635

636
      date_format = locale_format && Mode.normal ?
16✔
637
        "+%c" :
13✔
638
        "+%Y-%m-%d - %H:%M:%S"
3✔
639

640
      log.info("GetDateTime hwclock: #{@hwclock} real: #{real_time}")
16✔
641
      if !real_time && !Mode.config
16✔
642
        ds = 0
3✔
643
        if @diff != 0
3✔
644
          out2 = Convert.to_map(
3✔
645
            SCR.Execute(path(".target.bash_output"), "/usr/bin/date +%z")
646
          )
647
          tzd = Ops.get_string(out2, "stdout", "")
3✔
648
          log.info("GetDateTime tzd: #{tzd}")
3✔
649
          t = Builtins.tointeger(String.CutZeros(Builtins.substring(tzd, 1, 2)))
3✔
650
          if t != nil
3✔
651
            ds = Ops.add(ds, Ops.multiply(t, 3600))
3✔
652
            t = Builtins.tointeger(
3✔
653
              String.CutZeros(Builtins.substring(tzd, 3, 2))
654
            )
655
            ds = Ops.add(ds, Ops.multiply(t, 60))
3✔
656
            ds = Ops.unary_minus(ds) if Builtins.substring(tzd, 0, 1) == "-"
3✔
657
            log.info("GetDateTime ds: #{ds} diff: #{@diff}")
3✔
658
          end
659
        end
660
        cmd = tz_prefix +
3✔
661
          Builtins.sformat(
662
            "/usr/bin/date \"%1\" \"--date=now %2sec\"",
663
            date_format,
664
            Ops.multiply(ds, @diff)
665
          )
666
      else
667
        cmd = Builtins.sformat("/usr/bin/date \"%1\"", date_format)
13✔
668
      end
669
      log.info("GetDateTime cmd: #{cmd}")
16✔
670
      out = Convert.to_map(SCR.Execute(path(".target.bash_output"), cmd))
16✔
671
      local_date = Builtins.deletechars(Ops.get_string(out, "stdout", ""), "\n")
16✔
672

673
      log.info("GetDateTime local_date: '#{local_date}'")
16✔
674

675
      local_date
16✔
676
    end
677

678
    # Clear the internal map with timezones, so the timezone data could be
679
    # retranslated next time when they are needed
680
    def ResetZonemap
1✔
681
      @zonemap = []
10✔
682

683
      nil
10✔
684
    end
685

686
    # Return true if localtime should be proposed as default
687
    # Based on current hardware configuration:
688
    # Win partitions present or 32bit Mac
689
    def ProposeLocaltime
1✔
690
      vmware = SCR.Read(path(".probe.is_vmware"))
18✔
691
      @windows_partition || vmware || (Arch.board_mac && Arch.ppc32)
18✔
692
    end
693

694

695
    # Return proposal list of strings.
696
    #
697
    # @param [Boolean] force_reset If force_reset is true reset the module
698
    #   to the timezone stored in default_timezone.
699
    # @param [Boolean] language_changed
700
    #
701
    # @return        [Array] user readable description.
702
    def MakeProposal(force_reset, language_changed)
1✔
703
      Builtins.y2milestone("force_reset: %1", force_reset)
11✔
704
      Builtins.y2milestone(
11✔
705
        "language_changed: %1 user_decision %2 user_hwclock %3",
706
        language_changed,
707
        @user_decision,
708
        @user_hwclock
709
      )
710

711
      ResetZonemap() if language_changed
11✔
712

713
      if !@user_hwclock || force_reset
11✔
714
        @hwclock = "-u"
11✔
715
        @hwclock = "--localtime" if ProposeLocaltime()
11✔
716
      end
717
      if force_reset
11✔
718
        # If user wants to reset do it if a default is available.
719
        #
720
        if @default_timezone != ""
6✔
721
          Set(@default_timezone, true) # reset
1✔
722
        end
723

724
        # Reset user_decision flag.
725
        #
726
        @user_decision = false # no reset
6✔
727
      else
728
        # Only follow the language if the user has never actively chosen
729
        # a timezone. The indicator for this is user_decision which is
730
        # set from outside the module.
731
        #
732
        if @user_decision || Mode.autoinst ||
5✔
733
            ProductFeatures.GetStringFeature("globals", "timezone") != ""
734
          if language_changed
1✔
735
            Builtins.y2milestone(
1✔
736
              "User has chosen a timezone; not following language - only retranslation."
737
            )
738

739
            Set(@timezone, true)
1✔
740
          end
741
        else
742
          # User has not yet chosen a timezone ==> follow language.
743
          #
744
          local_timezone = GetTimezoneForLanguage(
4✔
745
            Language.language,
746
            "US/Eastern"
747
          )
748

749
          if local_timezone != ""
4✔
750
            Set(local_timezone, true)
4✔
751
            @default_timezone = local_timezone
4✔
752
          else
753
            if language_changed
×
754
              Builtins.y2error("Can't follow language - only retranslation")
×
755

756
              Set(@timezone, true)
×
757
            end
758
          end
759
        end
760
      end
761

762
      # label text (Clock setting)
763
      clock_setting = _("UTC")
11✔
764

765
      if @hwclock == "--localtime"
11✔
766
        # label text, Clock setting: local time (not UTC)
767
        clock_setting = _("Local Time")
×
768
      end
769

770
      # label text
771
      clock_setting = Ops.add(_("Hardware Clock Set To") + " ", clock_setting)
11✔
772

773
      date = GetDateTime(true, true)
11✔
774

775
      Builtins.y2milestone("MakeProposal hwclock %1", @hwclock)
11✔
776

777
      ret = [
778
        Ops.add(
11✔
779
          Ops.add(Ops.add(Ops.add(@name, " - "), clock_setting), " "),
780
          date
781
        )
782
      ]
783
      if @ntp_used
11✔
784
        # summary label
785
        ret = Builtins.add(ret, _("NTP configured"))
×
786
      end
787
      deep_copy(ret)
11✔
788
    end
789

790
    # Selection()
791
    #
792
    # Return a map of ids and names to build up a selection list
793
    # for the user. The key is used later in the Set function
794
    # to select this timezone. The name is a translated string.
795
    #
796
    # @param num [Integer] id of region like Africa or Europe. Region can be result of {#Set}
797
    #
798
    # @return [Array[Item()]        list of timezones for given region.
799
    #   Item Id can be used for Set or Probe functions. Then it contain name
800
    #   and false. So no item is preselected.
801
    # @see #Set()
802
    def Selection(num)
1✔
803
      zmap = get_zonemap
2✔
804

805
      trl = Builtins.maplist(Ops.get_map(zmap, [num, "entries"], {})) do |key, name|
2✔
806
        [name, key]
106✔
807
      end
808

809
      trl = Builtins.sort(trl) do |a, b|
2✔
810
        # bnc#385172: must use < instead of <=, the following means:
811
        # strcoll(x) <= strcoll(y) && strcoll(x) != strcoll(y)
812
        lsorted = Builtins.lsort([Ops.get(a, 0, ""), Ops.get(b, 0, "")])
296✔
813
        lsorted_r = Builtins.lsort([Ops.get(b, 0, ""), Ops.get(a, 0, "")])
296✔
814
        Ops.get_string(lsorted, 0, "") == Ops.get(a, 0, "") &&
296✔
815
          lsorted == lsorted_r
816
      end
817
      Builtins.y2debug("trl = %1", trl)
2✔
818

819
      Builtins.maplist(trl) do |e|
2✔
820
        Item(Id(Ops.get_string(e, 1, "")), Ops.get_string(e, 0, ""), false)
106✔
821
      end
822
    end
823

824
    # Return list of regions for timezone selection list
825
    def Region
1✔
826
      num = -1
×
827
      Builtins.maplist(get_zonemap) do |entry|
×
828
        num = Ops.add(num, 1)
×
829
        Item(Id(num), Ops.get_string(entry, "name", ""), false)
×
830
      end
831
    end
832

833

834
    # Save()
835
    #
836
    # Save timezone to target sysconfig.
837
    def Save
1✔
838
      if Mode.mode == "update"
4✔
839
        Builtins.y2milestone("not saving in update mode...")
1✔
840
        return
1✔
841
      end
842

843
      write_timezone
3✔
844

845
      SCR.Write(path(".sysconfig.clock.DEFAULT_TIMEZONE"), @default_timezone)
3✔
846

847
      SCR.Write(path(".sysconfig.clock"), nil) # flush
3✔
848

849
      Builtins.y2milestone("Save Saved data for timezone: <%1>", @timezone)
3✔
850

851
      adjtime = ReadAdjTime()
3✔
852
      if adjtime.nil? || adjtime.size == 3
3✔
853
        new     = adjtime.nil? ? ["0.0 0 0.0", "0"] : adjtime.dup
3✔
854
        new[2]  = @hwclock == "-u" ? "UTC" : "LOCAL"
3✔
855
        if adjtime.nil? || new[2] != adjtime[2]
3✔
856
          SCR.Write(path(".etc.adjtime"), new)
3✔
857
          Builtins.y2milestone("Saved /etc/adjtime with '%1'", new[2])
3✔
858
        end
859
      end
860

861
      CallMkinitrd() if @call_mkinitrd && !Stage.initial
3✔
862

863
      nil
3✔
864
    end
865

866

867
    # Return current date and time in the map
868
    def GetDateTimeMap
1✔
869
      ret = {}
1✔
870
      dparts = Builtins.filter(
1✔
871
        Builtins.splitstring(GetDateTime(false, false), " -:")
872
      ) { |v| Ops.greater_than(Builtins.size(v), 0) }
8✔
873

874
      Ops.set(ret, "year", Ops.get_string(dparts, 0, ""))
1✔
875
      Ops.set(ret, "month", Ops.get_string(dparts, 1, ""))
1✔
876
      Ops.set(ret, "day", Ops.get_string(dparts, 2, ""))
1✔
877
      Ops.set(ret, "hour", Ops.get_string(dparts, 3, ""))
1✔
878
      Ops.set(ret, "minute", Ops.get_string(dparts, 4, ""))
1✔
879
      Ops.set(ret, "second", Ops.get_string(dparts, 5, ""))
1✔
880

881
      Builtins.y2milestone("GetDateTimeMap dparts %1 ret %2", dparts, ret)
1✔
882
      deep_copy(ret)
1✔
883
    end
884

885
    def CheckTime(hour, minute, second)
1✔
886
      ret = true
9✔
887
      tmp = Builtins.tointeger(String.CutZeros(hour))
9✔
888
      return false if tmp == nil
9✔
889
      ret = ret && Ops.greater_or_equal(tmp, 0) && Ops.less_than(tmp, 24)
7✔
890
      tmp = Builtins.tointeger(String.CutZeros(minute))
7✔
891
      return false if tmp == nil
7✔
892
      ret = ret && Ops.greater_or_equal(tmp, 0) && Ops.less_than(tmp, 60)
7✔
893
      tmp = Builtins.tointeger(String.CutZeros(second))
7✔
894
      return false if tmp == nil
7✔
895
      ret = ret && Ops.greater_or_equal(tmp, 0) && Ops.less_than(tmp, 60)
6✔
896
      ret
6✔
897
    end
898

899
    def CheckDate(day, month, year)
1✔
900
      mdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
9✔
901
      ret = true
9✔
902
      yea = Builtins.tointeger(String.CutZeros(year))
9✔
903
      mon = Builtins.tointeger(String.CutZeros(month))
9✔
904
      da = Builtins.tointeger(String.CutZeros(day))
9✔
905
      return false if yea == nil || mon == nil || da == nil
9✔
906
      ret = ret && Ops.greater_or_equal(mon, 1) && Ops.less_or_equal(mon, 12)
6✔
907
      if Ops.modulo(yea, 4) == 0 &&
6✔
908
          (Ops.modulo(yea, 100) != 0 || Ops.modulo(yea, 400) == 0)
3✔
909
        Ops.set(mdays, 1, 29)
3✔
910
      end
911
      ret = ret && Ops.greater_or_equal(da, 1) &&
6✔
912
        Ops.less_or_equal(da, Ops.get_integer(mdays, Ops.subtract(mon, 1), 0))
913
      ret = ret && yea >= 1970 # bsc#1214144
6✔
914
      ret
6✔
915
    end
916

917
    # does the hwclock run on UTC only ? -> skip asking
918
    def utc_only
1✔
919
      Builtins.y2milestone(
×
920
        "Arch::sparc () %1 Arch::board_iseries () %2 Arch::board_chrp () %3 Arch::board_prep () %4",
921
        Arch.sparc,
922
        Arch.board_iseries,
923
        Arch.board_chrp,
924
        Arch.board_prep
925
      )
926

927
      Arch.sparc || Arch.board_iseries || Arch.board_chrp || Arch.board_prep
×
928
    end
929

930
    # save the initial data
931
    def PushVal
1✔
932
      @push = { "hwclock" => @hwclock, "timezone" => @timezone }
5✔
933
      Builtins.y2milestone("PushVal map %1", @push)
5✔
934

935
      nil
5✔
936
    end
937

938
    # restore the original data from internal map
939
    def PopVal
1✔
940
      Builtins.y2milestone(
×
941
        "before Pop: timezone %1 hwclock %2",
942
        @timezone,
943
        @hwclock
944
      )
945
      if Builtins.haskey(@push, "hwclock")
×
946
        @hwclock = Ops.get_string(@push, "hwclock", @hwclock)
×
947
      end
948
      if Builtins.haskey(@push, "timezone")
×
949
        @timezone = Ops.get_string(@push, "timezone", @timezone)
×
950
      end
951
      @push = {}
×
952
      Builtins.y2milestone(
×
953
        "after Pop: timezone %1 hwclock %2",
954
        @timezone,
955
        @hwclock
956
      )
957

958
      nil
×
959
    end
960

961
    # was anything modified?
962
    def Modified
1✔
963
      @modified || @timezone != Ops.get_string(@push, "timezone", @timezone) ||
1✔
964
        @hwclock != Ops.get_string(@push, "hwclock", @hwclock)
965
    end
966

967
    # AutoYaST interface function: Get the Timezone configuration from a map.
968
    # @param [Hash] settings imported map
969
    # @return success
970
    def Import(settings)
1✔
971
      settings = deep_copy(settings)
5✔
972
      # Read was not called -> do the init
973
      PushVal() if @push == {}
5✔
974

975
      if Builtins.haskey(settings, "hwclock")
5✔
976
        @hwclock = Ops.get_string(settings, "hwclock", "UTC") == "UTC" ? "-u" : "--localtime"
4✔
977
        @user_hwclock = true
4✔
978
      end
979
      # FIXME: this set modifies the system which is very unusual for an import operation
980
      Set(Ops.get_string(settings, "timezone", @timezone), true)
5✔
981
      true
5✔
982
    end
983

984
    # AutoYaST interface function: Return the Timezone configuration as a map.
985
    # @return [Hash] with the settings
986
    def Export
1✔
987
      ret = {}
3✔
988

989
      if(ProposeLocaltime() && @hwclock != "-u") ||
3✔
990
        (!ProposeLocaltime() && @hwclock == "-u")
3✔
991
        log.info("hwclock <#{@hwclock}> is the default value"\
2✔
992
                 " --> no export")
993
      else
994
        ret["hwclock"] = @hwclock == "-u" ? "UTC" : "localtime"
1✔
995
      end
996

997
      local_timezone = Timezone.GetTimezoneForLanguage(
3✔
998
        Language.language,
999
        "US/Eastern"
1000
      )
1001
      if local_timezone == @timezone
3✔
1002
        log.info("timezone <#{@timezone}> is the default value"\
1✔
1003
                 " --> no export")
1004
      else
1005
        ret["timezone"] = @timezone
2✔
1006
      end
1007

1008
      deep_copy(ret)
3✔
1009
    end
1010

1011
    # AutoYaST interface function: Return the summary of Timezone configuration as a map.
1012
    # @return summary string (html)
1013
    def Summary
1✔
1014
      Yast.import "HTML"
2✔
1015

1016
      clock_setting = _("UTC")
2✔
1017

1018
      if @hwclock == "--localtime"
2✔
1019
        # label text, Clock setting: local time (not UTC)
1020
        clock_setting = _("Local Time")
×
1021
      end
1022

1023
      # label text
1024
      clock_setting = Ops.add(_("Hardware Clock Set To") + " ", clock_setting)
2✔
1025

1026
      ret = [
1027
        # summary label
1028
        Builtins.sformat(_("Current Time Zone: %1"), @name),
2✔
1029
        clock_setting
1030
      ]
1031
      HTML.List(ret)
2✔
1032
    end
1033

1034
    # Checks whether the system has Windows installed
1035
    #
1036
    # @return [Boolean]
1037
    def system_has_windows?
1✔
1038
      # Avoid probing if the architecture is not supported for Windows
1039
      return false unless windows_architecture?
5✔
1040

1041
      disk_analyzer.windows_system?
3✔
1042
    rescue NameError => ex
1043
      # bsc#1058869: Don't enforce y2storage being available
1044
      log.warn("Caught #{ex}")
1✔
1045
      log.warn("No storage-ng support - not checking for a windows partition")
1✔
1046
      log.warn("Assuming UTC for the hardware clock")
1✔
1047
      false # No windows partition found
1✔
1048
    end
1049

1050
    # Determines whether timezone is read-only for the current product
1051
    #
1052
    # @return [Boolean] true if it's read-only; false otherwise.
1053
    def readonly
1✔
1054
      return @readonly unless @readonly.nil?
12✔
1055
      @readonly = ProductFeatures.GetBooleanFeature("globals", "readonly_timezone")
9✔
1056
    end
1057

1058
    # Product's default timezone when it's not defined in the control file.
1059
    FALLBACK_PRODUCT_DEFAULT_TIMEZONE = "UTC"
1✔
1060

1061
    # Determines the default timezone for the current product
1062
    #
1063
    # If not timezone is set, FALLBACK_PRODUCT_DEFAULT_TIMEZONE will be used.
1064
    # More information can be found on FATE#321754 and
1065
    # https://github.com/yast/yast-installation/blob/master/doc/control-file.md#installation-and-product-variables
1066
    #
1067
    # @return [String] timezone
1068
    def product_default_timezone
1✔
1069
      product_timezone = ProductFeatures.GetStringFeature("globals", "timezone")
6✔
1070
      product_timezone.empty? ? FALLBACK_PRODUCT_DEFAULT_TIMEZONE : product_timezone
6✔
1071
    end
1072

1073
    publish :variable => :timezone, :type => "string"
1✔
1074
    publish :variable => :hwclock, :type => "string"
1✔
1075
    publish :variable => :default_timezone, :type => "string"
1✔
1076
    publish :variable => :user_decision, :type => "boolean"
1✔
1077
    publish :variable => :user_hwclock, :type => "boolean"
1✔
1078
    publish :variable => :ntp_used, :type => "boolean"
1✔
1079
    publish :variable => :diff, :type => "integer"
1✔
1080
    publish :variable => :modified, :type => "boolean"
1✔
1081
    publish :variable => :windows_partition, :type => "boolean"
1✔
1082
    publish :variable => :call_mkinitrd, :type => "boolean"
1✔
1083
    publish :variable => :yast2zonetab, :type => "map <string, string>"
1✔
1084
    publish :variable => :obsoleted_zones, :type => "map <string, string>"
1✔
1085
    publish :function => :get_zonemap, :type => "list <map <string, any>> ()"
1✔
1086
    publish :function => :Set, :type => "integer (string, boolean)"
1✔
1087
    publish :function => :UpdateTimezone, :type => "string (string)"
1✔
1088
    publish :function => :Read, :type => "void ()"
1✔
1089
    publish :function => :Timezone, :type => "void ()"
1✔
1090
    publish :function => :CallMkinitrd, :type => "boolean ()"
1✔
1091
    publish :function => :SetTime, :type => "void (string, string, string, string, string, string)"
1✔
1092
    publish :function => :SystemTime2HWClock, :type => "void ()"
1✔
1093
    publish :function => :GetTimezoneForLanguage, :type => "string (string, string)"
1✔
1094
    publish :function => :SetTimezoneForLanguage, :type => "void (string)"
1✔
1095
    publish :function => :GetLanguageForTimezone, :type => "string (string)"
1✔
1096
    publish :function => :GetCountryForTimezone, :type => "string (string)"
1✔
1097
    publish :function => :GetTimezoneCountry, :type => "string (string)"
1✔
1098
    publish :function => :GetDateTime, :type => "string (boolean, boolean)"
1✔
1099
    publish :function => :ResetZonemap, :type => "void ()"
1✔
1100
    publish :function => :ProposeLocaltime, :type => "boolean ()"
1✔
1101
    publish :function => :MakeProposal, :type => "list <string> (boolean, boolean)"
1✔
1102
    publish :function => :Selection, :type => "list (integer)"
1✔
1103
    publish :function => :Region, :type => "list ()"
1✔
1104
    publish :function => :Save, :type => "void ()"
1✔
1105
    publish :function => :GetDateTimeMap, :type => "map ()"
1✔
1106
    publish :function => :CheckTime, :type => "boolean (string, string, string)"
1✔
1107
    publish :function => :CheckDate, :type => "boolean (string, string, string)"
1✔
1108
    publish :function => :utc_only, :type => "boolean ()"
1✔
1109
    publish :function => :PushVal, :type => "void ()"
1✔
1110
    publish :function => :PopVal, :type => "void ()"
1✔
1111
    publish :function => :Modified, :type => "boolean ()"
1✔
1112
    publish :function => :Import, :type => "boolean (map)"
1✔
1113
    publish :function => :Export, :type => "map ()"
1✔
1114
    publish :function => :Summary, :type => "string ()"
1✔
1115

1116
  protected
1✔
1117

1118
    # Whether the architecture of the system is supported by MS Windows
1119
    #
1120
    # @return [Boolean]
1121
    def windows_architecture?
1✔
1122
      Arch.x86_64 || Arch.i386
5✔
1123
    end
1124

1125
    def disk_analyzer
1✔
1126
      Y2Storage::StorageManager.instance.probed_disk_analyzer
×
1127
    end
1128

1129
    # Writes the timezone configuration
1130
    def write_timezone
1✔
1131
      if @timezone.nil? || @timezone.strip.empty?
3✔
1132
        log.warn("Timezone configuration not written. No value was given.")
1✔
1133
        return
1✔
1134
      end
1135

1136
      cmd = if Stage.initial
2✔
1137
        # do use --root option, running in chroot does not work
1138
        "/usr/bin/systemd-firstboot --root '#{Installation.destdir}' --timezone '#{@timezone}'"
1✔
1139
      else
1140
        # this sets both the locale (see "man localectl")
1141
        "/usr/bin/timedatectl set-timezone #{@timezone}"
1✔
1142
      end
1143

1144
      log.info "Making timezone setting persistent: #{cmd}"
2✔
1145

1146
      result = if Stage.initial
2✔
1147
        WFM.Execute(path(".local.bash_output"), cmd)
1✔
1148
      else
1149
        SCR.Execute(path(".target.bash_output"), cmd)
1✔
1150
      end
1151

1152
      if result["exit"] != 0
2✔
1153
        log.error "Timezone configuration not written. Failed to execute '#{cmd}'"
×
1154
        log.error "output: #{result.inspect}"
×
1155

1156
        # TRANSLATORS: the "%s" is replaced by the executed command
1157
        Report.Error(_("Could not save the timezone setting, the command\n%s\nfailed.") % cmd)
×
1158
      else
1159
        log.info "output: #{result.inspect}"
2✔
1160
      end
1161
    end
1162
  end
1163

1164
  Timezone = TimezoneClass.new
1✔
1165
  Timezone.main
1✔
1166
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc