HarmonyOS NEXT Development Case: World Clock Application
zhongcx

zhongcx @zhongcx

About: The best time to plant a tree was 10 years ago, the second-best time is now.

Joined:
Mar 6, 2025

HarmonyOS NEXT Development Case: World Clock Application

Publish Date: May 11
0 0

Image description

The following case demonstrates how to build a world clock application using HarmonyOS NEXT. This application supports dynamic time updates for multiple cities, search filtering, and highlighted text matching. Below is the code implementation with detailed explanations and optimized English comments.


Core Code Implementation

1. Data Model Class

import { i18n } from '@kit.LocalizationKit'; // Import localization module  
import { inputMethod } from '@kit.IMEKit'; // Import input method module  

@ObservedV2 // Observer decorator for state management  
class CityTimeInfo {  
  @Trace cityName: string = ""; // City name  
  @Trace currentTime: string = ""; // Current formatted time string  
  timeZone: i18n.TimeZone; // Time zone object  

  constructor(cityName: string, timeZone: i18n.TimeZone) {  
    this.cityName = cityName;  
    this.timeZone = timeZone;  
  }  

  @Trace isVisible: boolean = true; // Visibility flag for filtering  
}  
Enter fullscreen mode Exit fullscreen mode

2. Main Component Structure

@Entry // Entry component decorator  
@Component // Component decorator  
struct WorldClockApp {  
  @State private searchText: string = ''; // Search keyword  
  @State private cityTimeList: CityTimeInfo[] = []; // List of city time data  
  private lineColor: string = "#e6e6e6"; // Border color  
  private titleBackgroundColor: string = "#f8f8f8"; // Header background color  
  private textColor: string = "#333333"; // Text color  
  private basePadding: number = 4; // Base padding  
  private lineWidth: number = 2; // Border width  
  private rowHeight: number = 50; // Row height  
  private ratio: number[] = [1, 1]; // Column width ratio  
  private textSize: number = 14; // Base font size  
  private updateIntervalId = 0; // Timer ID for updates  

  // Update time for all cities  
  updateAllCityTimes() {  
    const locale = i18n.System.getSystemLocale(); // Get system locale  
    for (const cityTime of this.cityTimeList) {  
      const timeZoneId: string = cityTime.timeZone.getID();  
      const calendar = i18n.getCalendar(locale);  
      calendar.setTimeZone(timeZoneId);  

      // Format time components  
      const year = calendar.get("year").toString().padStart(4, '0');  
      const month = (calendar.get("month") + 1).toString().padStart(2, '0');  
      const day = calendar.get("date").toString().padStart(2, '0');  
      const hour = calendar.get("hour_of_day").toString().padStart(2, '0');  
      const minute = calendar.get("minute").toString().padStart(2, '0');  
      const second = calendar.get("second").toString().padStart(2, '0');  

      cityTime.currentTime = `${year}-${month}-${day} ${hour}:${minute}:${second}`;  
    }  
  }  

  // Timer control for updates  
  onPageShow(): void {  
    clearInterval(this.updateIntervalId);  
    this.updateIntervalId = setInterval(() => {  
      this.updateAllCityTimes();  
    }, 1000);  
  }  

  onPageHide(): void {  
    clearInterval(this.updateIntervalId);  
  }  

  // Highlight search text and filter cities  
  private highlightSearchText(cityTime: CityTimeInfo, keyword: string) {  
    let text = cityTime.cityName;  
    if (!keyword) {  
      cityTime.isVisible = true;  
      return [text];  
    }  

    let segments: string[] = [];  
    let lastMatchEnd: number = 0;  
    while (true) {  
      const matchIndex = text.indexOf(keyword, lastMatchEnd);  
      if (matchIndex === -1) {  
        segments.push(text.slice(lastMatchEnd));  
        break;  
      } else {  
        segments.push(text.slice(lastMatchEnd, matchIndex));  
        segments.push(text.slice(matchIndex, matchIndex + keyword.length));  
        lastMatchEnd = matchIndex + keyword.length;  
      }  
    }  
    cityTime.isVisible = segments.includes(keyword);  
    return segments;  
  }  

  // Initialize city data  
  aboutToAppear() {  
    const timeZoneIds: Array<string> = i18n.TimeZone.getAvailableIDs();  
    this.cityTimeList.push(new CityTimeInfo('Beijing (China)', i18n.getTimeZone()));  

    for (const id of timeZoneIds) {  
      const cityDisplayName = i18n.TimeZone.getCityDisplayName(id.split('/')[1], "zh-CN");  
      if (cityDisplayName) {  
        this.cityTimeList.push(new CityTimeInfo(cityDisplayName, i18n.getTimeZone(id)));  
      }  
    }  
    this.updateAllCityTimes();  
  }  

  // UI Layout  
  build() {  
    Column({ space: 0 }) {  
      Search({ value: $$this.searchText })  
        .margin(this.basePadding)  
        .fontFeature("\"ss01\" on");  

      Column() {  
        Row() {  
          Text('City')  
            .layoutWeight(this.ratio[0])  
            .textAlign(TextAlign.Center)  
            .fontSize(this.textSize)  
            .fontWeight(600);  
          Line().width(this.lineWidth).backgroundColor(this.lineColor);  
          Text('Time')  
            .layoutWeight(this.ratio[1])  
            .textAlign(TextAlign.Center)  
            .fontSize(this.textSize)  
            .fontWeight(600);  
        }  
        .height(this.rowHeight)  
        .borderWidth(this.lineWidth)  
        .backgroundColor(this.titleBackgroundColor);  
      }.width('100%').padding({ left: this.basePadding, right: this.basePadding });  

      Scroll() {  
        Column() {  
          ForEach(this.cityTimeList, (item: CityTimeInfo) => {  
            Row() {  
              Text() {  
                ForEach(this.highlightSearchText(item, this.searchText), (segment: string) => {  
                  ContainerSpan() {  
                    Span(segment)  
                      .fontColor(segment === this.searchText ? Color.White : Color.Black)  
                      .textBackgroundStyle({  
                        color: segment === this.searchText ? Color.Red : Color.Transparent  
                      });  
                  };  
                });  
              }  
              .layoutWeight(this.ratio[0]);  

              Line().width(this.lineWidth).backgroundColor(this.lineColor);  
              Text(item.currentTime)  
                .layoutWeight(this.ratio[1]);  
            }  
            .height(this.rowHeight)  
            .visibility(item.isVisible ? Visibility.Visible : Visibility.None);  
          });  
        }.width('100%');  
      }  
      .onScrollStart(() => this.onPageHide())  
      .onScrollStop(() => this.onPageShow())  
      .onTouch((event) => {  
        if (event.type == TouchType.Down) {  
          inputMethod.getController().stopInputSession();  
        }  
      });  
    }  
  }  
}  
Enter fullscreen mode Exit fullscreen mode

Key Features Explained

1. Dynamic Time Updates

  • The updateAllCityTimes method fetches the current time for each city using the i18n module.
  • A timer updates all cities every second, triggered by onPageShow and paused by onPageHide.

2. Search & Highlight

  • The highlightSearchText method splits city names into segments, highlighting matches with red backgrounds.
  • Visibility of cities is toggled based on search results.

3. Performance Optimization

  • Timer updates are paused during scrolling (onScrollStart/onScrollStop).
  • Input sessions are closed on touch events to improve responsiveness.

4. UI Layout

  • Uses Row and Column components for responsive layouts.
  • Styled borders and spacing to enhance readability.

Comments 0 total

    Add comment